@awebai/oats 0.25.8 → 0.26.0

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 (185) hide show
  1. package/README.md +8 -6
  2. package/bin/oats.mjs +576 -1714
  3. package/capabilities/oats-authoring/oats-package.json +2 -2
  4. package/capabilities/oats-authoring/oats.json +2 -2
  5. package/capabilities/oats-authoring/skills/integration-authoring/SKILL.md +46 -25
  6. package/capabilities/oats-authoring/skills/soul-craft/SKILL.md +13 -6
  7. package/capabilities/oats-aweb/bin/oats-aweb.mjs +334 -72
  8. package/capabilities/oats-aweb/injects/aweb.md +7 -2
  9. package/capabilities/oats-aweb/lib/binding-wire.mjs +107 -5
  10. package/capabilities/oats-aweb/lib/captured-native.mjs +1 -1
  11. package/capabilities/oats-aweb/lib/grant-custody.mjs +38 -0
  12. package/capabilities/oats-aweb/oats.json +14 -4
  13. package/capabilities/oats-jira/bin/oats-jira.mjs +4 -4
  14. package/capabilities/oats-jira/oats.json +2 -2
  15. package/capabilities/oats-jira/skills/jira-tasks/SKILL.md +6 -3
  16. package/capabilities/oats-linear/bin/oats-linear-hook.mjs +6 -4
  17. package/capabilities/oats-linear/oats.json +2 -2
  18. package/capabilities/oats-linear/skills/linear-tasks/SKILL.md +6 -0
  19. package/capabilities/oats-okf/bin/oats-okf.mjs +1 -1
  20. package/capabilities/oats-okf/lib/binding-wire.mjs +1 -1
  21. package/capabilities/oats-okf/lib/migration.mjs +2 -2
  22. package/capabilities/oats-okf/lib/sources.mjs +5 -4
  23. package/capabilities/oats-okf/lib/stores.mjs +40 -9
  24. package/capabilities/oats-okf/lib/worker.mjs +3 -3
  25. package/capabilities/oats-okf/oats.json +1 -1
  26. package/capabilities/oats-review/oats.json +3 -2
  27. package/docs/capabilities.md +218 -47
  28. package/docs/capability-manifest.schema.json +13 -4
  29. package/docs/configuration.md +17 -5
  30. package/docs/conventions.md +16 -26
  31. package/docs/design/2026-09-08-expert-assisted-deployment-proposal.md +1 -1
  32. package/docs/design/2026-09-13-knowledge-and-memory-direction.md +3 -3
  33. package/docs/design/2026-09-14-portable-souls-and-git-workspaces.md +2 -2
  34. package/docs/design/2026-09-15-portable-souls-handoff.md +2 -2
  35. package/docs/design/2026-09-15-portable-souls-implementation.md +1 -1
  36. package/docs/design/2026-09-20-redesign-program-board.md +2 -2
  37. package/docs/design/2026-09-23-workspace-module-contracts.md +1 -1
  38. package/docs/design/2026-09-23-workspace-v2-implementation-plan.md +1 -1
  39. package/docs/design/2026-09-24-desktop-phase-f-boundary.md +54 -10
  40. package/docs/design/2026-09-24-phase-d-plan.md +77 -0
  41. package/docs/design/2026-09-25-teams-contract.md +226 -0
  42. package/docs/design/README.md +3 -3
  43. package/docs/design/launch-configurations.md +20 -16
  44. package/docs/design/operations-contract.md +27 -10
  45. package/docs/desktop-cli-api.md +546 -264
  46. package/docs/desktop-instance-start.md +1 -1
  47. package/docs/desktop.md +7 -13
  48. package/docs/execution-targets.md +16 -18
  49. package/docs/first-team.md +14 -17
  50. package/docs/implementation.md +28 -59
  51. package/docs/integrations.md +88 -32
  52. package/docs/knowledge-capability-authoring.md +1 -1
  53. package/docs/knowledge-reference/package-craft.md +10 -8
  54. package/docs/knowledge-theory.md +1 -1
  55. package/docs/knowledge.md +10 -11
  56. package/docs/layers.md +16 -17
  57. package/docs/oats-local.schema.json +29 -1
  58. package/docs/oats-membership.schema.json +5 -3
  59. package/docs/oats-package.schema.json +2 -2
  60. package/docs/oats-workspace.schema.json +1 -1
  61. package/docs/{official-marketplace.md → official-catalog.md} +15 -16
  62. package/docs/packages.md +75 -52
  63. package/docs/release-notes/v0.22.0.md +1 -1
  64. package/docs/release-notes/v0.23.1.md +1 -1
  65. package/docs/release-notes/v0.25.9.md +23 -0
  66. package/docs/release-notes/v0.26.0.md +670 -0
  67. package/docs/schedules.md +48 -126
  68. package/docs/soul.schema.json +11 -4
  69. package/docs/souls-and-instances.md +56 -43
  70. package/docs/workspaces.md +80 -58
  71. package/injects/instance-boundary.md +1 -1
  72. package/injects/work-attached.md +1 -1
  73. package/injects/work-workspace.md +2 -2
  74. package/lib/{portable-files.mjs → bounded-read.mjs} +6 -6
  75. package/lib/{portable-values.mjs → canonical-json.mjs} +3 -12
  76. package/lib/capability-contract.mjs +110 -0
  77. package/lib/config-data.mjs +2 -2
  78. package/lib/core.mjs +700 -4824
  79. package/lib/digest.mjs +12 -0
  80. package/lib/instance-inspect.mjs +396 -0
  81. package/lib/instance-lifecycle.mjs +3 -4
  82. package/lib/instance-resolution.mjs +212 -26
  83. package/lib/instruction-composition.mjs +0 -20
  84. package/lib/materialize.mjs +6 -4
  85. package/lib/operator-dispatch.mjs +33 -13
  86. package/lib/packages.mjs +25 -190
  87. package/lib/provider-binding.mjs +4 -2
  88. package/lib/provider-reasons.mjs +3 -68
  89. package/lib/resolve.mjs +204 -68
  90. package/lib/schedule.mjs +97 -272
  91. package/lib/servers.mjs +13 -13
  92. package/lib/{portable-shape.mjs → shape.mjs} +4 -3
  93. package/lib/tree-copy.mjs +44 -0
  94. package/lib/workspace.mjs +125 -20
  95. package/package-catalog.json +7 -7
  96. package/package.json +1 -1
  97. package/skills/integration-authoring/SKILL.md +48 -40
  98. package/skills/oats-getting-started/SKILL.md +105 -110
  99. package/skills/oats-support/SKILL.md +2 -2
  100. package/skills/soul-craft/SKILL.md +13 -6
  101. package/bin/oats-pi-sdk-host.mjs +0 -17
  102. package/docs/2026-09-03-architecture-proposal.md +0 -642
  103. package/docs/artifact-approvals.schema.json +0 -7
  104. package/docs/captured-invocation-context.schema.json +0 -7
  105. package/docs/captured-resolution.schema.json +0 -7
  106. package/docs/design/package-engine-contract.md +0 -813
  107. package/docs/design/package-runtime-api.md +0 -588
  108. package/docs/desktop-succession.md +0 -57
  109. package/docs/execution-capsule.schema.json +0 -108
  110. package/docs/first-team-demo.md +0 -92
  111. package/docs/knowledge-migration.md +0 -147
  112. package/docs/migration-from-oas.md +0 -103
  113. package/docs/oats-config.schema.json +0 -172
  114. package/docs/oats-lock-v3.schema.json +0 -7
  115. package/docs/oats-lock.schema.json +0 -175
  116. package/docs/operating-team-migration.md +0 -470
  117. package/docs/portable.schema.json +0 -2512
  118. package/docs/provider-check-input.schema.json +0 -7
  119. package/docs/rebuild-to-v2.md +0 -511
  120. package/docs/workspace-adoption.md +0 -74
  121. package/injects/framework-workspace.md +0 -7
  122. package/injects/local-soul.md +0 -19
  123. package/injects/oats-portable.md +0 -20
  124. package/injects/oats.md +0 -11
  125. package/injects/portable-instance-boundary.md +0 -39
  126. package/injects/portable-work-directory.md +0 -29
  127. package/lib/artifact-approvals.mjs +0 -120
  128. package/lib/artifact-tree.mjs +0 -141
  129. package/lib/capability-artifacts.mjs +0 -179
  130. package/lib/capability-execution.mjs +0 -15
  131. package/lib/capability-inputs.mjs +0 -39
  132. package/lib/capability-provenance.mjs +0 -231
  133. package/lib/captured-action-shape.mjs +0 -21
  134. package/lib/captured-admission-shape.mjs +0 -20
  135. package/lib/captured-binding-file.mjs +0 -36
  136. package/lib/captured-dispatch.mjs +0 -66
  137. package/lib/captured-instance-index.mjs +0 -277
  138. package/lib/captured-invocation-context.mjs +0 -130
  139. package/lib/captured-launch-request.mjs +0 -66
  140. package/lib/captured-operation-process.mjs +0 -15
  141. package/lib/captured-pi-custody.mjs +0 -29
  142. package/lib/captured-pi-host.mjs +0 -167
  143. package/lib/captured-pi-outcome.mjs +0 -172
  144. package/lib/captured-resolutions.mjs +0 -275
  145. package/lib/captured-scaffold.mjs +0 -87
  146. package/lib/captured-selector.mjs +0 -28
  147. package/lib/captured-session-backend.mjs +0 -52
  148. package/lib/captured-source-receipt-file.mjs +0 -72
  149. package/lib/helper-injection-policy.mjs +0 -104
  150. package/lib/legacy-lock-codec.mjs +0 -106
  151. package/lib/manifest-settings.mjs +0 -84
  152. package/lib/package-closure.mjs +0 -48
  153. package/lib/package-materialization.mjs +0 -83
  154. package/lib/pi-sdk-host.mjs +0 -229
  155. package/lib/portable-artifacts.mjs +0 -115
  156. package/lib/portable-choices.mjs +0 -82
  157. package/lib/portable-composition.mjs +0 -136
  158. package/lib/portable-digest.mjs +0 -105
  159. package/lib/portable-identity.mjs +0 -40
  160. package/lib/portable-lock.mjs +0 -117
  161. package/lib/portable-onboarding-request.mjs +0 -49
  162. package/lib/portable-onboarding.mjs +0 -256
  163. package/lib/portable-package-preparation.mjs +0 -188
  164. package/lib/portable-policy.mjs +0 -44
  165. package/lib/portable-soul.mjs +0 -42
  166. package/lib/portable-state.mjs +0 -80
  167. package/lib/prepare-composition.mjs +0 -170
  168. package/lib/prepared-bindings.mjs +0 -92
  169. package/lib/prepared-resources.mjs +0 -127
  170. package/lib/provider-binding-broker.mjs +0 -65
  171. package/lib/provider-binding-wire.mjs +0 -116
  172. package/lib/readiness.mjs +0 -225
  173. package/lib/repository-observation.mjs +0 -226
  174. package/lib/resolution-shape.mjs +0 -393
  175. package/lib/schedule-capsule.mjs +0 -206
  176. package/lib/soul-constraints.mjs +0 -40
  177. package/lib/source-projection.mjs +0 -84
  178. package/lib/source-spec.mjs +0 -189
  179. package/lib/workspace-definition.mjs +0 -126
  180. package/lib/workspace-discovery.mjs +0 -146
  181. package/skills/oats/SKILL.md +0 -162
  182. package/skills/oats-config/SKILL.md +0 -164
  183. package/skills/oats-packages/SKILL.md +0 -184
  184. package/skills/oats-portable/SKILL.md +0 -115
  185. package/skills/oats-portable-artifacts/SKILL.md +0 -63
@@ -18,9 +18,11 @@ prints exactly one JSON object on stdout:
18
18
  ```
19
19
 
20
20
  `version` is the installed package's exact semver (e.g. `0.20.0`).
21
- Desktop 0.25 accepts `desktopApi === 1` and semver `>=0.22.0 <0.26.0`
22
- (the earlier Desktop 0.23 band was `>=0.22.0 <0.24.0`). This admits the paired
23
- 0.24 CLI without changing Desktop API v1. It does not establish complete captured
21
+ The Desktop accepts `desktopApi === 1` and gates on the kernel feature
22
+ `packages-no-approval` (semver range `>=0.25.8 <0.27.0`: the floor admits the
23
+ main-branch kernel before 0.26.0 is tagged; the feature fence is the real gate).
24
+ Earlier bands were `>=0.22.0 <0.26.0` (Desktop 0.25) and `>=0.22.0 <0.24.0`
25
+ (Desktop 0.23). It does not establish complete
24
26
  UI, backend, plugin, retirement or recovery parity; capability checks and explicit
25
27
  refusals below remain authoritative.
26
28
 
@@ -46,49 +48,348 @@ no progress prose (progress goes to stderr):
46
48
  - success (exit 0): `{"schemaVersion":1,"ok":true,"result":{...}}`
47
49
  - failure (nonzero exit): `{"schemaVersion":1,"ok":false,"error":{"code":"...","message":"..."}}`
48
50
 
49
- ## Souls and sources (`oats inspect --json`, `soulsApi: 1`, OATS 0.24.7+)
50
-
51
- Every entry in `result.souls[]` carries what the soul's **own `soul.yaml`
52
- declares**, parsed by the kernel — a consumer never parses YAML and never
53
- infers a field that is not there:
54
-
55
- - `soulsApi: 1`
56
- - `declarations: { requires, defaults, knowledge, teams, resources, children }` — each
57
- the declared object, or `null` when the section is absent (`children`
58
- since 0.24.8: `{spawn: boolean}`, see readiness policy).
59
- - `provenance: { kind, source, revision, path, workspaceRevision } | null` —
60
- where this soul copy came from, as recorded by the kernel when it created
61
- it (the 0.24 bootstrap recorded `packaged-definition` or
62
- `exported-edition-copy`; the 0.25 `oats onboard` creates no soul and records
63
- nothing here — see [`oats onboard`](#oats-onboard-onboardapi-2)).
64
- Souls created before 0.24.7 or authored by hand
65
- read `null`; render that as *unrecorded*, not as local or as anything else.
66
- - `readiness` — the soul's **declared sources**, joined against
67
- `result.capabilities[]` from the same payload. Distinct from launchability
68
- (`oats spawn`) and adoption (`oats prepare`); never a green "Ready".
69
- - `source: "recorded" | "unrecorded"`
70
- - `requirements: [{ capability, source, installed, approved, active, version }] | null`
71
- (`null` = nothing declared). `installed: false` = not in the inventory;
72
- `approved`/`active`/`version` are `null` when there is no inventory row.
73
- - `status: "undeclared" | "sources-installed" | "sources-missing" | "unknown"`
74
- - `declarationProblems: [{ code, message }]` — an unreadable file reports why;
75
- the soul is still listed.
76
-
77
- `result.sources` is the scope's **portable source context**: the distinct
78
- provenance sources its souls record.
51
+ ## Inspect, readiness and operation run on the workspace model (`operationsApi: 2`, `soulsApi: 2`, `readinessApi: 2`, OATS 0.26.0)
52
+
53
+ On a workspace deployment (an `oats-local.yaml` in reach of `--dir`), and for
54
+ any home whose `instance.json` records `modules`, these three commands read the
55
+ workspace model's own records and **never the classic config chain**. The probe
56
+ integers are the gate; there is no feature string. The probe's integer says
57
+ this kernel CAN answer the v2 shape. **Dispatch on the payload's own integer**.
58
+ There is no v1 shape any more (0.26.0 removed the classic chain's answers):
59
+ with no `oats-local.yaml` in reach and no `--home`, the three commands answer
60
+ `E_LOCAL_MISSING`; a `--home` whose `instance.json` records no `modules`
61
+ (spawned by an earlier kernel) answers `E_UNSUPPORTED_MODE` (re-spawn it from
62
+ the deployment); an unreadable `--home` answers `E_SESSION_UNKNOWN`.
63
+
64
+ **The captured/portable path was removed in 0.26.** A *captured home* (its `instance.json`
65
+ records `executionBinding`, `incarnationId` or `captured`: spawned through
66
+ 0.24–0.25's `prepare` / `--deployment --resolution`) answers `E_UNSUPPORTED_MODE`
67
+ (`details: {home, captured: true}`) to these three commands, to `session
68
+ start|restart` and to its in-home commands; `oats retire` still works on it, and
69
+ its result's `warnings[]` names each capability whose retire hook did NOT run
70
+ (what it created is not revoked). `oats status --json` / `oats doctor --json`
71
+ name captured homes once, in `problems[]`, as `legacy-captured-home` `{instances,
72
+ homes, message}`. The captured selectors `--deployment`, `--resolution` and
73
+ `--artifact-set`, and an inherited `OATS_DEPLOYMENT`/`OATS_RESOLUTION`, are refused
74
+ by every command except `version` (`E_UNSUPPORTED_MODE`, `details.selector` or
75
+ `details.inherited`); `oats prepare` and `oats inspect --request` are removed
76
+ verbs (`E_UNKNOWN_COMMAND`, `details: {removed, replacement}`). The version
77
+ document no longer carries `capturedDispatchApi` / `capturedDispatchActions`.
78
+
79
+ | Command | Integer (probe and payload) | 0.25.x value |
80
+ |---|---|---|
81
+ | `oats inspect --json` | `operationsApi: 2` (top level); each `souls[]` row `soulsApi: 2` | 1 / 1 |
82
+ | `oats readiness --json` | `readinessApi: 2` | 1 |
83
+ | `oats operation run --json` | `operationsApi: 2` on the result | absent |
84
+
85
+ The probe's `soulsApi` follows the inspect soul rows. The `oats souls --json`
86
+ document keeps its own `soulsApi: 1`, because its shape did not change (see
87
+ [`oats souls`](#oats-capabilities---dir---json-capabilitiesapi-1-oats-souls---dir---json-soulsapi-1)).
88
+
89
+ **The subject is an instance or a soul, never a scope.** Pass `--home <abs>`
90
+ or `--soul <name>`. A workspace deployment with neither is `E_BAD_ARGS`. An
91
+ `oats-local.yaml` that exists but cannot be read is reported with its own
92
+ error code, never answered from the classic chain. For
93
+ inspect, the message points to `oats souls` and `oats capabilities`, the
94
+ scope-wide lists.
95
+ - `--home` selects the instance, from its `instance.json` and the module copies
96
+ under `<home>/.oats/modules/`. Everything is as spawned.
97
+ - `--soul` selects the soul, resolved exactly as a spawn of it would be:
98
+ discovery, then soul `capabilities:` plus workspace defaults, then the lock.
99
+ - A v2 home lives at `<deployment>/agents/<soul>/instances/<name>`, and its
100
+ deployment is derived from that path. `<deployment>/oats-local.yaml` must
101
+ exist exactly there (never found by walking up), otherwise
102
+ `E_HOME_MISMATCH`. A v2 spawn ignores an ambient `PI_AGENTS_ROOT` /
103
+ `OATS_ROOT`, so its homes always have this layout.
104
+ - `--dir`, if given with `--home`, must be that home's deployment
105
+ (`E_HOME_MISMATCH`). `--agents-root`, if given, must be
106
+ `<deployment>/agents` (`E_HOME_MISMATCH` with `--home`, `E_SOUL_UNKNOWN`
107
+ with `--soul`).
108
+
109
+ **Gone from every payload:** `scope` (`context`, `chain`, `team`,
110
+ `agentsRoots`), config `levels`, `activation {declaredAt, target, level,
111
+ source}`, `currentConfig`, `snapshot.drift`, `health {trusted, approved,
112
+ locked, installedIntegrity}`, soul `provenance`/`readiness`, and the scope's
113
+ portable `sources`. A capability's origin is its module's `from` (member
114
+ commit, or package version + commit + integrity). Its settings are the merged
115
+ payload the spawn recorded for a home, or the resolution computes for a soul.
116
+
117
+ ### `oats inspect (--home <abs> | --soul <name> [--dir <d>]) --json` → `operationsApi: 2`
79
118
 
80
119
  ```json
81
- {"soulsApi":1,"kind":"recorded-provenance","note":null,
82
- "items":[{"kind":"exported-edition-copy","source":"git:https://…/oats.git","revision":"<sha>","path":"souls/oats-setup-expert","workspaceRevision":"<sha>","souls":["oats-setup-expert"]}]}
120
+ {"operationsApi":2,"kernel":"0.26.0",
121
+ "subject":{"kind":"instance","instance":"release-manager-x","home":"/w/agents/release-manager/instances/release-manager-x","soul":"release-manager"},
122
+ "workspace":{"key":"github.com/northwind/agents","name":null,"deployment":"/w","commit":"461b9c24…","standalone":false},
123
+ "souls":[{"soulsApi":2,"name":"release-manager","repoKey":"github.com/northwind/agents","commit":"461b9c24…","team":"engineering",
124
+ "kind":null,"path":null,"description":"Cuts, verifies and announces platform releases.","work":"worktree","runtime":null,"model":null,
125
+ "declarations":{"requires":null,"defaults":null,"knowledge":{"owns":"release-manager","reads":["platform-engineer"]},"teams":null,"resources":null,"children":null,
126
+ "capabilities":{"nw-release-tooling":{"from":"here"},"nw-deploy":{"from":"package"}}},
127
+ "declarationProblems":[],
128
+ "instructions":{"file":"/w/agents/release-manager/souls/461b9c24929c/AGENTS.md","text":"# release-manager\n…","truncated":false}}],
129
+ "layers":{"knowledge":{"id":"oats.okf","from":"workspace"},"messaging":{"id":null,"from":null},"tasks":{"id":null,"from":null}},
130
+ "capabilities":[
131
+ {"id":"nw-house-style","version":"0.0.0-workspace","layer":null,"command":null,
132
+ "from":{"kind":"member","repoKey":"github.com/northwind/agents","commit":"461b9c24…"},
133
+ "dir":"/w/agents/release-manager/instances/release-manager-x/.oats/modules/nw-house-style","settings":{},"declares":[],"compatibility":{"ok":true,"range":">=0.25.0","kernel":"0.26.0"},"missingRequires":[],"operations":[]},
134
+ {"id":"oats.okf","version":"2.1.3","layer":"knowledge","command":"okf",
135
+ "from":{"kind":"package","package":"oats.okf","version":"2.1.3","commit":"71f53649…","integrity":"sha256-5019…","repoKey":"github.com/awebai/oats-okf"},
136
+ "dir":"/w/agents/release-manager/instances/release-manager-x/.oats/modules/oats.okf",
137
+ "settings":{"owns":"release-manager","reads":["platform-engineer"],"state-dir":"/srv/okf"},"declares":["bindings-file","git-timeout","harvest-model","harvest-runtime","state-dir"],"compatibility":{"ok":true,"range":">=0.24.4","kernel":"0.26.0"},"missingRequires":[],
138
+ "operations":[{"name":"inspect","kind":"view","command":"inspect","context":"home","description":"…","args":[],"argv":["okf","inspect"],"available":true,"reason":null}]}],
139
+ "knowledge":{"provider":"oats.okf","version":"2.1.3","operations":[{"name":"inspect","kind":"view","available":true,"reason":null}]},
140
+ "instance":{"home":"/w/agents/release-manager/instances/release-manager-x","instance":"release-manager-x","agent":"release-manager",
141
+ "runtime":"pi","model":null,"yolo":null,"launched":false,"createdAt":"<iso>","resolution":"7217670b…",
142
+ "soulDir":"/w/agents/release-manager/souls/461b9c24929c",
143
+ "instructions":{"file":"/w/agents/release-manager/instances/release-manager-x/AGENTS.md","text":"…","truncated":false,
144
+ "sources":[{"source":"kernel:instance-boundary","file":"…"},{"source":"capability:oats.okf","file":"…/.oats/modules/oats.okf/injects/okf.md"}]}},
145
+ "identity":null,"problems":[]}
146
+ ```
147
+
148
+ - `subject` is `{kind:"instance", instance, home, soul}` for `--home` (the
149
+ `home` as you passed it) or `{kind:"soul", soul, repoKey, commit, team}` for
150
+ `--soul`.
151
+ - `workspace.deployment` is canonical (realpath). `workspace.name` is
152
+ observed, so it is `null` on `inspect --home`: that command never contacts
153
+ the remotes, and `instance.json` records the workspace `key`, not its name.
154
+ Identify the workspace by `key`.
155
+ - `souls` holds exactly the subject's soul. For a home, it is read from the
156
+ recorded `soulDir` (the per-commit copy the instance incarnates), with
157
+ `path: null`. For a soul, it is the member's current definition, with
158
+ `path` inside the member repository. `kind` is `member` or `external`.
159
+ It is observed from discovery, so it is `null` on `inspect --home`
160
+ (readiness `--home` observes it).
161
+ `declarations` gains `capabilities` (the soul's own `capabilities:`).
162
+ - `layers.<layer>` is `{ id, from }`: the capability filling the slot
163
+ (`null` when empty) and where it came from (feature `layers-from`):
164
+ `"soul"` (the soul's own `capabilities:`), `"workspace"`
165
+ (`defaults.<slot>` or `defaults.capabilities`) or `"team:<label>"`
166
+ (`defaults.byTeam.<label>.capabilities`). `from` is `null` for an empty
167
+ slot. A soul answers from its resolution now. A home answers what its spawn
168
+ recorded, even after the workspace changes. A home spawned before
169
+ `layers-from` recorded nothing, so its `from` is `null`.
170
+ - `capabilities[]` lists the subject's resolved modules, sorted by id:
171
+ - `dir` is the home's module copy, or `null` for a soul (nothing is
172
+ materialized to answer inspect).
173
+ - `settings` is the merged provider payload.
174
+ - `compatibility` is `{ ok, range, kernel }`: the manifest's
175
+ `compatibility.oats` (`null` when none) against the running kernel. A
176
+ soul's resolution refuses an incompatible module (`E_CAPABILITY_INCOMPATIBLE`),
177
+ so `ok: false` appears only for a home, together with a
178
+ `capability-incompatible` entry in `problems`.
179
+ - `declares` lists the setting keys the manifest declares (`settings.<key>`),
180
+ sorted; names only, never descriptions or defaults; `[]` when it declares
181
+ none. Gate on feature `settings-declared` (e.g. offer a Teams choice only
182
+ when the messaging module declares `join`).
183
+ - `missingRequires` lists the manifest `requires` commands absent from PATH.
184
+ - `operations[].available` is `false` with a `reason` when it cannot run
185
+ here: a `context: "home"` operation for a soul subject says `needs a
186
+ running home (--home)`.
187
+ - `instance` is `null` for a soul. For a home, `instructions.sources` names
188
+ each composed inject in order.
189
+ - A soul whose resolution is refused (for example, a package the lock does
190
+ not provide) is an error for inspect (`E_PACKAGE_MISSING`,
191
+ `E_PACKAGE_INTEGRITY`, `E_CAPABILITY_MISSING`, `E_LOCK_SCHEMA`). Readiness
192
+ reports the same condition as a failing item.
193
+
194
+ ### `oats readiness (--home <abs> | --soul <name> [--dir <d>]) [--policy] --json` → `readinessApi: 2`
195
+
196
+ ```json
197
+ {"readinessApi":2,
198
+ "subject":{"kind":"soul","soul":"release-manager","repoKey":"github.com/northwind/agents","commit":"461b9c24…","team":"engineering"},
199
+ "selector":{"kind":"soul","soul":"release-manager","agentsRoot":null,"dir":"/w"},"at":"<iso>",
200
+ "checks":{
201
+ "installed":{"status":"pass","items":[
202
+ {"subject":"oats.okf","status":"pass","required":true,"reason":null,"producer":"workspace resolution",
203
+ "evidence":{"from":{"kind":"package","package":"oats.okf","version":"2.1.3","commit":"71f53649…","integrity":"sha256-5019…"}},"remedy":null,"capability":{"id":"oats.okf"}}]},
204
+ "configured":{"status":"not-applicable","items":[]},
205
+ "member":{"status":"pass","items":[
206
+ {"subject":"member github.com/northwind/agents","status":"pass","required":true,"reason":null,"producer":"workspace discovery",
207
+ "evidence":{"repoKey":"github.com/northwind/agents","workspace":"github.com/northwind/agents","commit":"461b9c24…"},"remedy":null}]},
208
+ "providers":{"status":"fail","items":[
209
+ {"subject":"oats.okf","status":"fail","required":true,"reason":"setting state-dir is required (absolute host path)","producer":"provider binding check",
210
+ "evidence":null,"remedy":null,"capability":{"id":"oats.okf"},
211
+ "result":{"status":"needs-configuration","problems":[{"code":"needs-configuration","message":"setting state-dir is required (absolute host path)"}],"warnings":[]}}]}},
212
+ "summary":{"ready":false,"required":3,"pass":2,"fail":1,"unknown":0,
213
+ "byCapability":[{"capability":{"id":"oats.okf"},"checks":{"installed":"pass","configured":"not-applicable","member":"not-applicable","providers":"fail"},"ownReady":false,"ready":false}],
214
+ "subjectBlockers":[]},
215
+ "notes":["…"]}
216
+ ```
217
+
218
+ For `--home`, `subject` is `{kind:"instance", instance, home, soul}`, and
219
+ `selector` is `{kind:"home", home, soul, agentsRoot}`. The selector echoes
220
+ your arguments byte-exact, as before. It is now a top-level field, not
221
+ `subject.selector`.
222
+
223
+ The four checks are `installed | configured | member | providers`, each
224
+ `{status, items}` with the item fields as before (`subject, status, required,
225
+ reason, producer, evidence, remedy`, plus `capability {id}` on
226
+ per-capability items). Item and check statuses are `pass | fail | unknown |
227
+ not-applicable`. **`summary.ready`** means every required item passes or is
228
+ not-applicable, and at least one required item exists. `byCapability` and
229
+ `subjectBlockers` keep their 0.24.9 meaning over the four new checks.
230
+
231
+ - **`installed`**:
232
+ - For `--home` (producer `instance modules`): each recorded module, `pass`
233
+ when its copy under `<home>/.oats/modules/<id>/` holds its `oats.json`.
234
+ A missing copy fails, with remedy "spawn a new instance".
235
+ - For `--soul` (producer `workspace resolution`): each resolved module, with
236
+ its `from` as evidence.
237
+ - A resolution refusal is one failing item carrying `code` (`E_PACKAGE_MISSING`,
238
+ `E_PACKAGE_INTEGRITY`, `E_CAPABILITY_MISSING`, `E_LOCK_SCHEMA` or
239
+ `E_REQUIREMENT_INACTIVE`), the kernel's message as `reason`, its details as
240
+ `evidence`, and `remedy: "oats sync (…)"`. That covers a soul whose
241
+ packages are not locked, or whose lock no longer matches. The item's
242
+ `subject` is the soul; it lands in `summary.subjectBlockers`.
243
+ - **`configured`** (producer `capability manifest`): each module's manifest
244
+ `requires` command (`evidence.command`), `pass` on PATH and `fail`
245
+ otherwise, with the manifest's `install` hint as the remedy. It is `not-applicable` when nothing declares a
246
+ requirement. Settings problems are the provider's to say, in `providers`.
247
+ - **`member`** (producer `workspace discovery`): the soul's member
248
+ repository is confirmed in the workspace. It is the host's `members:` with
249
+ the member's `oats-membership.yaml` backlink, as `oats workspace status`
250
+ reports it. The kernel never reads `oats.yaml` here.
251
+ - `fail` when the backlink is not confirmed; the remedy names
252
+ `oats-membership.yaml`.
253
+ - `unknown` when discovery could not be read.
254
+ - `not-applicable` (`required: false`) for an external soul (it has no
255
+ member backlink) and on a standalone view (decision 10, an allowed mode:
256
+ membership is declared there, never confirmed). The reason starts with
257
+ `standalone view (explicit | unreadable-host)`, and
258
+ `evidence.standaloneReason` carries the reason code. A standalone soul can
259
+ therefore read Ready.
260
+ - Never login, never team registration.
261
+ - **`providers`** (producer `provider binding check`): for each module whose
262
+ manifest declares `binding`, the kernel runs the provider's own check
263
+ (`binding.check`). It relays **the provider's answer verbatim** as
264
+ `item.result: {status, problems: [{code, message}], warnings: [{code,
265
+ message}]}`. The status maps to the item:
266
+
267
+ | `result.status` | item `status` |
268
+ |---|---|
269
+ | `ready` | `pass` |
270
+ | `needs-configuration` | `fail` |
271
+ | `authorization-required` | `fail` |
272
+ | `unavailable` | `unknown` |
273
+
274
+ The reason is the first problem's message (`null` on pass).
275
+ `authorization-required` and `unavailable` were added in 0.26.0 (additive):
276
+ treat an unrecognized status as `unknown` and show `result` as sent.
277
+ - `warnings` is always present (`[]` when the provider sends none). It
278
+ **never changes the status** and is not counted in `summary`. A ready
279
+ binding can still say, for example, that end-to-end encryption is off:
280
+ show it next to the pass. A `warnings` that is not an array of `{code,
281
+ message}` strings makes the whole answer `unknown`, the same as a
282
+ malformed `problems`.
283
+ - A provider that cannot answer is `unknown`, with `item.problems` carrying
284
+ its error `{code, message}` and `result: null`. That covers:
285
+ - a refusal (`ok:false`, whose code is relayed);
286
+ - an invalid answer (`provider-unavailable`, see the wire below);
287
+ - a timeout;
288
+ - a module tree that cannot be made available.
289
+ - **One time budget per readiness read**: 60 s for all provider checks
290
+ together, and at most 30 s for each. Checks the budget does not reach are
291
+ not run; they are `unknown` with code `time-budget-exhausted`.
292
+ - For `--home` the check runs from the home's module copy, as the home's
293
+ hooks do. For `--soul` it runs from the module in the deployment's module
294
+ store (the tree `oats <ns> …` dispatch uses). A store tree is used only
295
+ while its content digest matches the digest verified when it was fetched
296
+ at the locked commit. A drifted tree is fetched again, and a fetch that
297
+ does not verify is `E_PACKAGE_INTEGRITY` (the item is `unknown` with that
298
+ code).
299
+ - A module without `binding` has no item; the check is `not-applicable`
300
+ when there are none.
301
+ - This check reads the provider; it does not bind. A spawn's fail-closed
302
+ hooks are unchanged.
303
+ - **Removed:** `trusted` and its `signature` block (declaring a package in
304
+ `packages:` is the trust decision). `--verify-signatures` answers
305
+ `E_BAD_ARGS`. `enrolled` is now `member`.
306
+
307
+ `--policy` is **kept**: it means the same without the chain. With `--home` it
308
+ is the instance's recorded, enforced policy (`instance.json` `policy`, plus
309
+ the recorded work mode). With `--soul` it is the soul's declaration
310
+ (`children.spawn`, `work`), `enforced: false`. The shape is unchanged:
311
+
312
+ ```json
313
+ "policy":{"childSpawns":{"allowed":true,"enforced":true,"origin":{"kind":"default","detail":"no declaration: children allowed"}},
314
+ "worktrees":{"allowed":false,"mode":"directory","enforced":true,"origin":{"kind":"work-mode","detail":"work: directory"}}}
315
+ ```
316
+
317
+ **The provider check wire (the request `binding.check` receives).** The
318
+ request is one JSON line on stdin, and the provider answers one envelope line
319
+ on stdout:
320
+
321
+ ```json
322
+ {"schemaVersion":1,"phase":"check","slot":"knowledge","capability":"oats.okf","settings":{"…":"the merged payload"},
323
+ "input":{"context":{"kind":"workspace","workspace":"<workspace key>","deployment":"/w","soul":"release-manager","team":"engineering",
324
+ "instance":"release-manager-x","home":"/w/agents/…/release-manager-x"},
325
+ "action":{"kind":"readiness"}}}
326
+ ```
327
+
328
+ ```json
329
+ {"schemaVersion":1,"phase":"check","slot":"knowledge","capability":"oats.okf","ok":true,
330
+ "result":{"status":"ready","problems":[],"warnings":[]}}
83
331
  ```
84
332
 
85
- `kind: "none-recorded"` (empty `items`, explanatory `note`) means no soul in the
86
- scope records a portable **source address** — a soul may still carry a
87
- `provenance` of kind `packaged-definition` with `source: null`. Say "no portable
88
- source recorded"; do not infer "local" or "authored" from this state.
89
- Workspace imports adopted onto a deployment will appear here at their pinned
90
- revisions when that adoption is recorded on the deployment; nothing is
91
- enumerated from a source repository that the deployment does not record.
333
+ The environment is the provider's module environment:
334
+ - `OATS_CAPABILITY`, `OATS_SETTINGS`, `OATS_CLI_BIN` and `OATS_WORKSPACE`;
335
+ - the team variables (`OATS_TEAM_*`, `OATS_WORKSPACE_NAME`/`_KEY`);
336
+ - `OATS_AGENT` (the soul), and `OATS_SOUL` when the soul directory is known;
337
+ - for a home, `OATS_INSTANCE` and `OATS_INSTANCE_HOME`.
338
+
339
+ For a home, `OATS_WORKSPACE_NAME` is `""` until spawn records the workspace
340
+ name (planned).
341
+
342
+ Ambient `OATS_*`/`PI_*` is removed. For a soul, `instance` and `home` are
343
+ `null`. The answer is decoded by the binding wire's response rules:
344
+ - the process exits 0;
345
+ - stdout is exactly one JSON document within the wire limits;
346
+ - the envelope has exactly `schemaVersion`, `phase`, `slot`, `capability`,
347
+ `ok` and `result` (or `error`), echoing the request's first four;
348
+ - `result` has `status`, `problems` and optionally `warnings`, and nothing else;
349
+ - `ready` carries no problems;
350
+ - problems and warnings are `{code, message}` strings. Their codes are the
351
+ provider's own and are not checked against `binding.reasons`.
352
+
353
+ Anything else is `unknown` (`provider-unavailable`). The check executable must
354
+ resolve (realpath) inside its module directory and be a regular file; otherwise
355
+ the item is `unknown` (`resource-not-found`). The request carries no
356
+ `binding`. A provider whose check
357
+ still requires one answers `invalid-binding`, and readiness reports it as
358
+ `unknown`.
359
+
360
+ ### `oats operation run <layer>:<name> (--home <abs> | --soul <name> [--dir <d>]) [--arg k=v …] --json` → `operationsApi: 2`
361
+
362
+ ```json
363
+ {"operationsApi":2,"operation":"knowledge:inspect","capability":"oats.okf","version":"2.1.3","argv":["okf","inspect"],
364
+ "cwd":"/w/agents/release-manager/instances/release-manager-x",
365
+ "target":{"home":"/w/agents/release-manager/instances/release-manager-x","instance":"release-manager-x"},
366
+ "result":{"documents":[{"label":"Working state (STATE.md)","kind":"markdown","text":"…"}]}}
367
+ ```
368
+
369
+ The provider is the module that fills `<layer>`:
370
+ - for `--home`, the home's module copy, with its recorded settings;
371
+ - for `--soul`, the resolved module, materialized into the deployment's module
372
+ store if needed.
373
+
374
+ The rest of the contract is unchanged ([operations contract](design/operations-contract.md)):
375
+ - errors: `E_OPERATION_UNKNOWN`, `E_OPERATION_UNAVAILABLE` (also a
376
+ `context: "home"` operation without `--home`), `E_CAPABILITY_REQUIRES`;
377
+ - the receipt rules and the `E_OPERATION_TIMEOUT` / `E_OPERATION_RESULT`
378
+ unconfirmed outcomes.
379
+
380
+ There is no `E_CAPABILITY_BLOCKED` (no trust gate). `cwd` is the home for a
381
+ `context: "home"` operation, and the deployment otherwise.
382
+
383
+ **Remote.** `--server` routes as before. The destination must advertise
384
+ `operations` with `operationsApi` 1 or 2; a 0.26 CLI routes to either.
385
+ Payload shapes are the destination kernel's.
386
+
387
+ ## Souls and sources (`oats inspect --json`, `soulsApi: 1`) — removed in 0.26.0
388
+
389
+ The classic scope document (`souls[].provenance`, `souls[].readiness`, the
390
+ scope's portable `sources`) was removed with the classic config chain.
391
+ `oats inspect` answers only [`soulsApi: 2`](#oats-inspect---home---soul---dir---json-operationsapi-2)
392
+ rows; the soul's declarations are in `oats souls --json`.
92
393
 
93
394
  ## Instance Git state (`oats instance git|diff`, `instanceGitApi: 1`, OATS 0.24.7+)
94
395
 
@@ -258,8 +559,8 @@ deduplicated per run. Where the run launched or targeted an instance, a
258
559
  session to open (the existing `oats session` surface); the kernel does not
259
560
  copy transcripts. `nextRun`/`lastRun`/`executionStatus` are unchanged. The
260
561
  Schedules view (frame 08) renders `recentRuns` as the recent-runs list and the
261
- transcript pointer as the handoff; captured-policy definitions are preserved
262
- as they are (definition fields are untouched by this addition).
562
+ transcript pointer as the handoff (definition fields are untouched by this
563
+ addition). A stored captured definition (removed in 0.26) lists as `invalid`.
263
564
 
264
565
  ### History API 3 (`scheduleHistoryApi: 3`, feature `schedule-read-2`, OATS 0.24.13+) — K8b
265
566
 
@@ -332,15 +633,16 @@ executable, runtime packages, child-spawn policy) and returns what the spawn
332
633
 
333
634
  ```json
334
635
  {"spawnPreviewApi":1,"preview":true,"agent":"dev","kind":"persistent","instance":"dev-fix-login","home":"/abs/agents/dev/instances/dev-fix-login",
335
- "repo":"/abs/repo","work":"worktree","runtime":"claude","model":"opus","modelSource":"soul","launchConfig":null,"yolo":false,"backend":"tmux",
636
+ "repo":"/abs/repo","work":"worktree","runtime":"claude","model":"opus","modelSource":"explicit","launchConfig":null,"yolo":false,"backend":"tmux",
336
637
  "branch":"agents/dev-fix-login","base":{"ref":"HEAD","oid":"<oid>"},"worktree":"/abs/agents/dev/instances/dev-fix-login/work",
337
638
  "relation":null,"parentInstance":null,"policy":{"childSpawns":{"allowed":true,"origin":{"kind":"default","detail":"…"}}},
338
639
  "executable":"/abs/bin/claude","capabilities":["oats.core"],"skills":["oats-operate","oats-souls"],"task":"…"}
339
640
  ```
340
641
 
341
- - **Name / work area**: `instance` is the canonical name (`<agent>-<purpose>`,
342
- de-duplicated with `-2`, `-3`…); `home` and `worktree` are the canonical
343
- paths. The renderer never derives paths.
642
+ - **Name / work area**: `instance` is the name: by default the derived shape
643
+ `<agent>-<purpose>` (de-duplicated with `-2`, `-3`…), or exactly the
644
+ `--name <slug>` the caller gave (see *Instance names* below); `home` and
645
+ `worktree` are the canonical paths. The renderer never derives paths.
344
646
  - **Branch / base** (worktree mode): `branch` defaults to `agents/<instance>`
345
647
  (`--branch <name>` overrides; validated); `base` is `--base <ref>` resolved
346
648
  to its commit oid (default `HEAD`). `E_BRANCH_EXISTS` and `E_BASE_UNKNOWN`
@@ -376,15 +678,13 @@ the pre-fix marker and is never accepted for dispatch.
376
678
  import; `--instructions-file`/`--def-file` refused with `E_BAD_ARGS`). Test:
377
679
  the deployment tree is byte-identical after a success, a refusal and an
378
680
  unknown-soul preview.
379
- **Workspace deployments (0.25.1+) — one stated exception**: the first preview
380
- of a workspace soul may populate the deployment's per-commit soul cache
381
- (`agents/<soul>/souls/<commit>/`, the swappable `agents/<soul>/soul` pointer,
382
- `soulFetched: true` in the result). That cache is derived, content-addressed
383
- and idempotent — the same member commit yields the same bytes, a later
384
- preview of the same commit writes nothing — and nothing else moves: no lock,
385
- no event, no home, no instance. A Desktop treats a preview as
386
- side-effect-free for everything it shows; it must not assume the deployment
387
- directory's byte-identity across the FIRST preview of a soul or commit.
681
+ **Workspace deployments (0.26.0+)**: this holds for the FIRST preview of a
682
+ soul or commit too. A preview reads the soul from the deployment's per-commit
683
+ cache (`agents/<soul>/souls/<commit>/`) when a spawn already filled it, else
684
+ fetches it to a temporary copy outside the deployment and removes it
685
+ (`soulFetched: true` in the result). Only a spawn fills the cache or moves the
686
+ `agents/<soul>/soul` pointer. (0.25.x previews populated the cache: that stated
687
+ exception is gone.)
388
688
  - **Exact root**: `spawn <soul> --agents-root <abs>` binds the soul to that root
389
689
  (as inspect/readiness take it) — no team-soul / capability-agent / importable-
390
690
  def fallback; mismatch → `E_SOUL_UNKNOWN`. The preview echoes
@@ -395,7 +695,19 @@ the pre-fix marker and is never accepted for dispatch.
395
695
  per-module payload each provider will receive (`{ "<cap>": {…} }`, exactly
396
696
  `settings.<cap>` of the preview) — so a confirmed apply binds every
397
697
  provider fact (an identity choice, a delivery mode) **by value**; a Desktop
398
- that changes a provider field re-previews. `instances[].identity` (status)
698
+ that changes a provider field re-previews. From 0.26.0 the merged payload
699
+ includes the manifest's declared setting defaults (`settings.<key>.default`,
700
+ the lowest layer), and the preview's **`settingsOrigins.<cap>`** maps each
701
+ leaf of `settings.<cap>` (a JSON pointer, e.g. `/identity/mode`) to
702
+ `{ kind, at }`: `kind` is `manifest-default` | `workspace` | `soul` |
703
+ `host` | `spawn` — the last layer that set it. (`workspace-team` no longer
704
+ appears since teams amendment K: a label's `byTeam` entry is not merged into
705
+ the settings; it is in `teams[].payload`.) —
706
+ and `at` names where (`oats.json#/settings/identity/default`,
707
+ `soul.yaml#/messaging`, `oats-local.yaml#/settings/<cap>`,
708
+ `--provider <cap>`, …). A Desktop labels `manifest-default` values
709
+ "Default" from this, instead of hardcoding them (feature
710
+ **`settings-origins`**). `instances[].identity` (status)
399
711
  and `selected.identity` (inspect) carry the served principal a messaging
400
712
  provider reported: `{ mode: "local"|"global", alias, team, address|null,
401
713
  resident|null, grant?: { id, expiresAt, scopes }, provider }`; absent when
@@ -429,7 +741,12 @@ the pre-fix marker and is never accepted for dispatch.
429
741
  immediately after the decision check; a concurrent spawn that lost refuses
430
742
  **`E_PLACEMENT_TAKEN`** having touched nothing. Two concurrent applies of
431
743
  one decision yield exactly one home. There is no wider lock; this
432
- reservation is the guarantee.
744
+ reservation is the guarantee. Instance names are deployment-wide
745
+ (0.26.0), so right after its reservation a spawn re-checks the whole agents
746
+ root: when another soul's concurrent spawn reserved the same name, it
747
+ removes its own empty reservation and refuses (`E_INSTANCE_NAME_TAKEN` for a
748
+ `--name`, `E_PLACEMENT_TAKEN` for a derived name). At most one wins, and
749
+ possibly neither.
433
750
  - Gate confirmation AND the exec owner on `spawn-preview-2` +
434
751
  `spawn-apply-2` + `spawn-idempotency`; a legacy local request on such a CLI
435
752
  is refused by the Desktop (`E_PLAN_REQUIRED`), not routed around the fence.
@@ -468,75 +785,15 @@ the pre-fix marker and is never accepted for dispatch.
468
785
  (provider contract), auto-PR (P1/ADE write approval), branch enumeration
469
786
  (producer seam).
470
787
 
471
- ## Readiness quartet, signatures, enforced policy (`oats readiness`, `readinessApi: 1`, OATS 0.24.8+)
472
-
473
- > **0.25 status — the producers below are the 0.24 tier.** The readiness DTO
474
- > (`readinessApi: 1`) still ships unchanged, but its four checks are *produced*
475
- > by the classic observers: `installed` by `oats list` over the
476
- > `.agents/capabilities/installed/` tier, `trusted` by the per-artifact approval
477
- > that `oats trust` wrote, `configured` by `oats-config.yaml` activation, and
478
- > `enrolled` by the `oats.yaml` backlink. On a **workspace deployment**
479
- > (`oats-local.yaml` present) none of those sources exists: nothing is installed,
480
- > approval is per package version in `oats-lock.json` v3 (`oats sync`), activation
481
- > is derived (workspace defaults ⊕ soul `capabilities:`), and membership is
482
- > `oats-membership.yaml` observed over the remotes. `oats list` and `oats trust`
483
- > are removed verbs (`E_UNKNOWN_COMMAND`), so a `remedy` naming them cannot be
484
- > run. Treat the field names and producer strings as the stable wire shape they
485
- > are; for the workspace-model facts read `oats spawn <soul> --preview --json`
486
- > (`modules[]` with from/commit/digest — the "installed" and "configured"
487
- > truth), `oats sync --json` `approvalNeeded[]` (the "trusted" truth) and
488
- > `oats workspace status --json` (the "enrolled" truth). Re-basing the quartet on
489
- > those producers is an open thread of [the workspace model](#workspace-model-workspaceapi-2);
490
- > when it lands it will be announced as a new feature name, not a silent change of
491
- > `readinessApi: 1`.
492
-
493
- `oats readiness [--soul <name>] [--home <abs>] [--verify-signatures] [--policy] [--dir <d>] --json`
494
- is the first-run readiness view (frame 09) and the Capabilities readiness rows
495
- (frame 04). Every fact is derived from the **same** data `oats inspect` reports
496
- — never a second opinion — and rolled into four checks:
497
-
498
- ```json
499
- {"readinessApi":1,"subject":{"kind":"soul","name":"dev"},"at":"<iso>",
500
- "checks":{
501
- "installed": {"status":"pass","items":[{"subject":"oats.core","status":"pass","required":true,"reason":null,"producer":"oats list","evidence":{"version":"1.1.3","integrity":"sha256-…","origin":"installed"},"remedy":null}]},
502
- "trusted": {"status":"fail","items":[{"subject":"oats.core","status":"fail","required":true,"reason":"executable surface not approved","producer":"artifact approval","evidence":{"integrity":"sha256-…"},"remedy":"oats trust oats.core",
503
- "signature":{"status":"unknown","signer":null,"reason":"signature verification needs a network fetch; pass --verify-signatures"}}]},
504
- "configured":{"status":"pass","items":[{"subject":"oats.core activation","status":"pass","required":true,"producer":"oats-config.yaml","evidence":{"target":"declared","level":"/abs"},"remedy":null}]},
505
- "enrolled": {"status":"not-applicable","items":[{"subject":"workspace membership","status":"not-applicable","required":false,"producer":"oats.yaml","reason":"standalone deployment: no workspace declared in oats.yaml"}]}},
506
- "summary":{"ready":false,"required":3,"pass":2,"fail":1,"unknown":0},
507
- "notes":["…"]}
508
- ```
788
+ ## Readiness quartet (`readinessApi: 1`) — removed in 0.26.0
509
789
 
510
- - Each check is `pass | fail | unknown | not-applicable`; items carry
511
- `subject, status, required, reason, producer, evidence, remedy`. **"Ready" is
512
- `summary.ready`**: every *required* item passes (or is not-applicable) and
513
- there is at least one required item — never inferred from an empty set.
514
- - **`installed`**: artifact present, locked, integrity matches. **`trusted`**:
515
- executable approval of the exact artifact (`oats trust`). Separately,
516
- `signature {status: verified | unsigned | unknown | invalid | not-applicable,
517
- signer: {id, label} | null, reason}` — the source commit's **verified Git
518
- signature**, named signer or nothing. It is `unknown` unless
519
- `--verify-signatures` (a network fetch of that one commit; `git log %G?`);
520
- a catalog URL, repository owner or byte hash is never a signer. Render
521
- "Trusted · signed by <label>" only for `verified`.
522
- - **`configured`**: activation for the subject, runtime-package requirements
523
- (`missingRequires`), runtime-settings problems. **`enrolled`**: workspace
524
- **member admission** (decision §3) — `not-applicable` for a standalone
525
- deployment (no `workspace:` in `oats.yaml`), `unknown` until admission is
526
- verified against the workspace observation, `pass`/`fail` when it is. Never
527
- login, never team registration; "Skip" leaves it not-applicable, never pass.
528
- *(The readiness producer still reads the 0.24 `oats.yaml` backlink; under the
529
- workspace model membership is `oats-membership.yaml` observed by
530
- `oats workspace status` — re-basing this item is an open thread.)*
531
- - Subject: `--soul <name>` scopes required items to the soul's declared
532
- requirements; without it, to the scope's active capabilities.
533
-
534
- `--policy` adds the **enforced** policy view with origins:
790
+ The quartet (`installed | trusted | configured | enrolled`), its signature
791
+ verification (`--verify-signatures`, feature `readiness-verify`) and the
792
+ scope subject were removed with the classic config chain. `oats readiness`
793
+ answers only [`readinessApi: 2`](#oats-readiness---home---soul---dir---policy---json-readinessapi-2);
794
+ `--verify-signatures` is `E_BAD_ARGS`.
535
795
 
536
- ```json
537
- "policy":{"childSpawns":{"allowed":false,"enforced":true,"origin":{"kind":"soul","detail":"children.spawn: false in soul.yaml"}},
538
- "worktrees":{"allowed":true,"mode":"worktree","enforced":true,"origin":{"kind":"work-mode","detail":"work: worktree"}}}
539
- ```
796
+ ### Enforced child-spawn policy (`--policy`)
540
797
 
541
798
  `childSpawns` is **enforced by the spawn route**: `soul.yaml` may declare
542
799
  `children: {spawn: false}`; `oats spawn --allow-child-spawns | --no-child-spawns`
@@ -550,58 +807,6 @@ instance's recorded (enforced) one; with only `--soul` it is the declaration
550
807
  (`enforced: false`). It is a lifecycle-authority claim, not an OS sandbox —
551
808
  the UI says so.
552
809
 
553
- ### Slice-5 producer pins (0.24.9+; all additive — gate items on field presence)
554
-
555
- - **`configured` is EFFECTIVE activation.** `activation.enabled` is the resolved
556
- verdict for the subject; a capability *declared* for the soul but disabled is
557
- `fail` with reason `declared for soul <n> but disabled (…)`. Declaration is
558
- never activation.
559
- - **Trust is not-applicable for data-only capabilities.** The inspect row now
560
- carries `health.executableSurface` (manifest commands/hooks/launch env — what
561
- `oats trust` approves). No surface → `trusted` item `not-applicable`, reason
562
- `no executable surface`, whatever the lock records. This is why a fresh
563
- `oats trust <package> --all-capabilities` "skipped" `oats.core` and readiness
564
- still said fail before 0.24.9.
565
- - **Typed linkage on every item**: `capability {id, level, scope}` and
566
- `origin {kind: requires|declares|default|inventory, target}`; plus
567
- `summary.byCapability[] {capability, origin, required, checks{installed,
568
- trusted, configured, enrolled}, ownReady, ready}` — the SAME items regrouped,
569
- no second observation. `ownReady` is the capability's own four verdicts;
570
- `ready` is `ownReady` AND no **subject-level blocker** — items that belong to
571
- no capability (workspace membership, soul declarations) are listed in
572
- `summary.subjectBlockers[] {check, subject, status}` and block every row.
573
- A per-capability row never says ready while the subject is blocked, and a
574
- row's verdict is never promoted to the subject's `summary.ready`. Render
575
- per-capability rows from this; never parse subjects.
576
- - **Selector echo**: `subject.selector` = the arguments **as given, byte-exact,
577
- no realpath** — `{kind:"scope", dir|null}` · `{kind:"soul", soul,
578
- agentsRoot|null, dir|null}` · `{kind:"home", home, soul, agentsRoot|null}`.
579
- Compare with what you sent, byte for byte; never filesystem-normalize a
580
- response path. The canonical scope is `subject.context` (may differ from
581
- `dir`, e.g. `/var` vs `/private/var` on macOS).
582
- - **Unreadable member document** (`oats.yaml` unreadable, or `workspace:`
583
- present but not a mapping) → `enrolled` item `unknown` with
584
- `evidence.file`, never `not-applicable`. A declared backlink stays `unknown`
585
- with reason `reciprocal admission not observed …` until the CLI fetches the
586
- workspace's members (K11).
587
- - **Captured homes refuse**: `readiness --home <captured>` →
588
- `E_UNSUPPORTED_MODE` (`details.captured: true`) before any current-config
589
- interpretation.
590
- - **`--agents-root <abs>`** is accepted with `--soul` (and with `--home`), as
591
- inspect takes it — pin the exact root you admitted.
592
- - **Signature verification (feature `readiness-verify`)**: `--verify-signatures`
593
- is bounded custody — **one total budget per readiness read** (120 s default)
594
- shared by every capability's fetch and verify (an exhausted budget refuses the
595
- remaining capabilities with `budget-exhausted`, no fetch), each Git child in
596
- its own process group and the **whole group** SIGKILLed on timeout or failure,
597
- scratch repositories removed on normal exit and on SIGINT/SIGTERM/SIGHUP, `GIT_CONFIG_GLOBAL
598
- =/dev/null` + no system config + no prompts/askpass, **only https/ssh**
599
- transports. `signature.failure` is `null` or `{code}` from the closed set
600
- `transport-not-allowed | fetch-failed | fetch-timeout | budget-exhausted |
601
- verifier-failed | verifier-timeout | cannot-check`; `signature.reason` is a
602
- fixed sentence, **never stderr**. Gate the *Verify signatures…* action on the
603
- feature name; keep it an explicit user action.
604
-
605
810
  ## Lifecycle plans — Stop and Remove (`lifecycleApi: 1`, OATS 0.24.8+)
606
811
 
607
812
  The Desktop's Stop and Remove confirmations render **plans**: a read-only
@@ -693,6 +898,12 @@ location. The receipt says so:
693
898
  nothing is lost; retry or pass `--discard-worktree`.
694
899
  - Non-worktree modes report `retention: null`. Quarantine/rollback paths keep
695
900
  their removal semantics.
901
+ - A recovery (`workRecovery`, `workRecoveries[]`) is `{path, classes, bytes,
902
+ outputs?, repoCopy?}` (0.26.0: `bytes`, `outputs`): `bytes` is the recovery's
903
+ own size; `outputs: {paths: [{path, bytes}], bytes}` names what it copied
904
+ beyond tracked state — a worktree's untracked and ignored paths, or a
905
+ directory's work entries — grouped by top-level entry, largest first. Absent
906
+ when only home bytes were copied.
696
907
  - The Remove dialog's "also delete worktree / branch" checkboxes map to these
697
908
  two flags; the kernel never touches a PR.
698
909
  - **Guarded apply** (what a GUI sends): `oats retire <i> --plan-revision <rev>
@@ -730,7 +941,8 @@ location. The receipt says so:
730
941
  `retire-retention`, `readiness`, `spawn-preview`, `instance-events`,
731
942
  `schedule-history`, and carries the API integers (`instanceGitApi`, `soulsApi`,
732
943
  `lifecycleApi`, `readinessApi`, `spawnPreviewApi`, `eventsApi`,
733
- `scheduleHistoryApi`). **Gate on these, never on a version string and never by
944
+ `scheduleHistoryApi`, `operationsApi`). In 0.26.0, `soulsApi`, `readinessApi`
945
+ and `operationsApi` are **2** ([the workspace-model inspect](#inspect-readiness-and-operation-run-on-the-workspace-model-operationsapi-2-soulsapi-2-readinessapi-2-oats-0260)). **Gate on these, never on a version string and never by
734
946
  optimistic invocation**: an older CLI ignores an unknown `--plan` on `retire`
735
947
  and *retires*. Absent feature → the view is unavailable. (`catalog` was the
736
948
  0.24 `oats catalog` verb's flag; the verb is removed under the workspace model
@@ -772,13 +984,12 @@ machine in the directory the operator chooses (any existing folder). It writes
772
984
  `<dir>/oats-local.yaml` (`{ schemaVersion: 2, workspace: <ref> }`), creates
773
985
  `<dir>/agents/` (the instance homes), then runs exactly the `oats sync` body
774
986
  over the directory just written — discover over the remotes, confirm
775
- membership, resolve `packages:`, approve (TTY) or list what needs approval,
776
- write `oats-lock.json`. It installs nothing, creates no soul, spawns nothing
987
+ membership, resolve `packages:`, write `oats-lock.json`. It installs nothing, creates no soul, spawns nothing
777
988
  and writes no `oats-config.yaml`; the member clones and the setup-expert spawn
778
989
  are printed as next steps. `<dir>` defaults to cwd; `--dir <d>` is the same
779
990
  argument (give it once). `--workspace` is required and must be a ref
780
991
  `lib/remote.mjs` parses (`E_REPO_REF`) — checked **before** anything is
781
- written. Captured selectors are refused (`E_BAD_ARGS`).
992
+ written. Captured selectors are refused (`E_UNSUPPORTED_MODE`: the captured/portable path was removed in 0.26).
782
993
 
783
994
  ```json
784
995
  {"onboardApi":2,
@@ -793,13 +1004,9 @@ written. Captured selectors are refused (`E_BAD_ARGS`).
793
1004
  ```
794
1005
 
795
1006
  - `sync` is the `syncApi: 1` report of the first sync (members, packages,
796
- changes, `approvalNeeded`, `problems`); `lock` is the lock it wrote.
797
- - **Exit `2` with `ok: true`** when `sync.approvalNeeded` is non-empty. Approve
798
- non-interactively with `oats sync --dir <dir> --approve <id>@<version> …`
799
- (0.25.2+; `<version>` is `approvalNeeded[].version` verbatim — a catalog
800
- version, or the full commit OID for a git-pinned package); a Desktop renders
801
- `approvalNeeded[].executables` (the digest of the executable set it is
802
- approving) and `targets`, then runs that command. Exit `0` otherwise.
1007
+ changes, `problems`); `lock` is the lock it wrote. Exit `0` on success —
1008
+ there is no approval-pending outcome (0.26.0, feature
1009
+ `packages-no-approval`; earlier kernels exited `2` with `approvalNeeded`).
803
1010
  - `hosting` states decision 26 (the kernel cannot see forge visibility, so it
804
1011
  reports `hostIsMember` and the rule rather than judging).
805
1012
  - `next.clone[]` is one row per **confirmed** member (`url` = what the
@@ -822,12 +1029,18 @@ written. Captured selectors are refused (`E_BAD_ARGS`).
822
1029
 
823
1030
  ### `oats sync [--dir <d>] --json` → `syncApi: 1`
824
1031
 
825
- Discovers, confirms membership, resolves `packages:` to commits, writes
826
- `oats-lock.json` (lockfileVersion 3), reports. **Exit `2` with `ok: true`** when
827
- the lock was written but approvals are pending (`approvalNeeded` non-empty).
828
- Approve with repeatable `--approve <id>@<version>` (0.25.2+, non-interactive;
829
- `<version>` = `approvalNeeded[].version` verbatim); the interactive prompt is
830
- the TTY fallback, not the contract. Exit `0` otherwise.
1032
+ Discovers, confirms membership, resolves `packages:` to commits + integrity,
1033
+ writes `oats-lock.json` (lockfileVersion 3), reports; exit `0` on success.
1034
+ **No package approval** (0.26.0, human decision 2026-09-24; feature
1035
+ `packages-no-approval`): declaring a package in `packages:` is the trust
1036
+ decision. The report has no `approvalNeeded`, package rows no `approved`,
1037
+ `changes[]` rows no `approvalNeeded`; there is no prompt and no exit `2`, and
1038
+ `--approve` is `E_BAD_ARGS`. A lock written by an earlier kernel keeps working
1039
+ (its `approved` records are ignored and dropped on the next write). The fields
1040
+ went away without an API-number bump — `syncApi`, `workspaceStatusApi` and
1041
+ `capabilitiesApi` stay `1`; the removal is signalled by the feature string
1042
+ alone — so a consumer reading `approvalNeeded`, `approval` or `approved` must
1043
+ gate that on the absence of `packages-no-approval`.
831
1044
 
832
1045
  ```json
833
1046
  {"syncApi":1,
@@ -839,12 +1052,9 @@ the TTY fallback, not the contract. Exit `0` otherwise.
839
1052
  "souls":["tools-expert"],"capabilities":["acme-tools-dev"],"publishes":{"package":"acme.tools","version":"0.4.0"}},
840
1053
  {"key":"github.com/acme/billing","name":"billing","commit":"<oid>","confirmed":false,"status":"no-backlink","detail":"github.com/acme/billing@… has no oats-membership.yaml","team":null,
841
1054
  "souls":[],"capabilities":[],"publishes":null}],
842
- "packages":[{"id":"oats.okf","version":"2.1.3","source":"catalog:oats.okf","commit":"<oid>","integrity":"sha256-…","capabilities":["oats.okf"],
843
- "approved":{"executables":"sha256-…","at":"<iso>"}},
844
- {"id":"acme.tools","version":"0.4.0","source":"git:github.com/acme/tools@v0.4.0","commit":"<oid>","integrity":"sha256-…","capabilities":["acme-deploy","acme-lint"],"approved":null}],
845
- "changes":[{"id":"acme.tools","from":null,"to":"0.4.0","commit":"<oid>","approvalNeeded":true}],
846
- "approvalNeeded":[{"id":"acme.tools","version":"0.4.0","commit":"<oid>","executables":"sha256-…",
847
- "targets":["acme-deploy: command apply → bin/acme-deploy.mjs","acme-deploy: hook spawn → bin/acme-deploy.mjs"]}],
1055
+ "packages":[{"id":"oats.okf","version":"2.1.3","source":"catalog:oats.okf","commit":"<oid>","integrity":"sha256-…","capabilities":["oats.okf"]},
1056
+ {"id":"acme.tools","version":"0.4.0","source":"git:github.com/acme/tools@v0.4.0","commit":"<oid>","integrity":"sha256-…","capabilities":["acme-deploy","acme-lint"]}],
1057
+ "changes":[{"id":"acme.tools","from":null,"to":"0.4.0","commit":"<oid>"}],
848
1058
  "problems":[]}
849
1059
  ```
850
1060
 
@@ -854,17 +1064,14 @@ the TTY fallback, not the contract. Exit `0` otherwise.
854
1064
  capabilities are **not** in `capabilities[]`; the non-collapse rule).
855
1065
  - `changes[]`: `from` = previously locked version or `null`; `to` = `null` when
856
1066
  the package was dropped from `packages:` and from the lock.
857
- - `approvalNeeded[].targets` are human-readable lines
858
- (`<cap>: command|hook <name> → <relpath>`); `executables` is the digest an
859
- approval would record.
860
1067
  - `problems[]`: `{ code, path, message, repoKey? }` — per-item discovery
861
1068
  problems (`E_WORKSPACE_SCHEMA`, `E_TEAM_UNKNOWN`, `E_REMOTE_*`, …). Never an
862
1069
  abort: an unreadable member directory is a problem of that member.
863
1070
  - Errors: `E_LOCAL_MISSING`, `E_WORKSPACE_SCHEMA { path, problems[] }`,
864
1071
  `E_REMOTE_UNREADABLE`, `E_LOCK_SCHEMA`, `E_PACKAGE_MISSING` (catalog has no
865
1072
  such id), `E_PACKAGE_INTEGRITY { why: "branch" | locked/observed }`,
866
- `E_PACKAGE_UNAPPROVED` (approval digest no longer matches),
867
- `E_PACKAGE_MANIFEST`, `E_REPO_REF`.
1073
+ `E_PACKAGE_MANIFEST`, `E_REPO_REF`, `E_BAD_ARGS` (`--approve`: package
1074
+ approval was removed).
868
1075
 
869
1076
  ### `oats package add <id> <version|git:<repo>@<ref>> | remove <id> [--dir] --json`
870
1077
 
@@ -896,18 +1103,28 @@ found to check the declaration even though it does not edit it. `E_USAGE`,
896
1103
  "declaredPackages":["acme.tools","oats.okf"],
897
1104
  "unsynced":[],
898
1105
  "stale":[],
899
- "approval":{"approved":["oats.okf"],"needed":["acme.tools"]},
900
1106
  "external":[{"source":"git:github.com/oss-collective/experts@<oid>","soul":"security-reviewer","team":"unassigned"}],
901
- "problems":[]}
1107
+ "problems":[],
1108
+ "warnings":[]}
902
1109
  ```
903
1110
 
1111
+ `warnings[]` (feature `teams`, also in the `sync` report): `{ code, label,
1112
+ souls, paths, message }` — one `unmapped-team-label` per label that is in
1113
+ `teams:` but not in `messaging.byTeam`, naming its souls (sorted) and each
1114
+ soul's `<repoKey>:<path>#/team`; sorted by label. Never a problem.
1115
+
904
1116
  `unsynced` = declared in `packages:` but not in the lock (run `sync`);
905
1117
  `stale` = locked but no longer declared. Read-only: does not write the lock.
1118
+ (0.26.0: the `approval` object is gone with package approval.)
906
1119
 
907
1120
  ### `oats capabilities [--dir] --json` → `capabilitiesApi: 1` · `oats souls [--dir] --json` → `soulsApi: 1`
908
1121
 
909
- Every **non-private** item of every confirmed member, external souls, and
910
- locked package capabilities, sorted by name then origin. `origin` is the
1122
+ Every item of every confirmed member, external souls, and locked package
1123
+ capabilities, sorted by name then origin. Souls have no private mode (their
1124
+ `private` is always `false`); a repo-owned capability is listed with
1125
+ `private: true` — usable only by its own repo's souls. The Desktop shows its
1126
+ "Repo owned" section when `version --json` lists the `capabilities-private`
1127
+ feature. `origin` is the
911
1128
  human string (`member <key> @ <8-char commit>` | `package <id> v<version>` |
912
1129
  `external <key> @ <commit>`); `kind` is the machine field. `team` is the label
913
1130
  or `"unassigned"`.
@@ -918,7 +1135,7 @@ or `"unassigned"`.
918
1135
  {"name":"acme-house-style","origin":"member github.com/acme/agents @ 3f2a9c1e","kind":"member","repoKey":"github.com/acme/agents","commit":"<oid>",
919
1136
  "team":"global","private":false,"path":"capabilities/acme-house-style","layer":null,"version":"0.0.0-workspace"},
920
1137
  {"name":"oats.okf","origin":"package oats.okf v2.1.3","kind":"package","package":"oats.okf","version":"2.1.3","commit":"<oid>",
921
- "team":"unassigned","private":false,"approved":true}],
1138
+ "team":"unassigned","private":false}],
922
1139
  "problems":[]}
923
1140
  ```
924
1141
 
@@ -932,8 +1149,9 @@ or `"unassigned"`.
932
1149
  ```
933
1150
 
934
1151
  Package capabilities of declared-but-unsynced packages are absent until `sync`.
935
- (`soulsApi: 1` is also the integer of the existing `oats inspect --json`
936
- declarations block; the two payloads are distinguished by their command.)
1152
+ (`oats souls --json` keeps `soulsApi: 1` in 0.26.0: its shape is unchanged.
1153
+ The probe's `soulsApi` is **2** because it tracks the `oats inspect --json`
1154
+ soul rows. The two payloads are distinguished by their command.)
937
1155
 
938
1156
  ### `oats spawn <soul> … --preview --json` — additions (Preview API 2 unchanged)
939
1157
 
@@ -943,10 +1161,10 @@ between preview and apply is `E_DECISION_STALE`):
943
1161
 
944
1162
  ```json
945
1163
  {"modules":[
946
- {"name":"acme-release-tooling","from":{"kind":"member","repoKey":"github.com/acme/agents","commit":"<oid>"},"layer":null,"private":false,
1164
+ {"name":"acme-release-tooling","from":{"kind":"member","repoKey":"github.com/acme/agents","commit":"<oid>"},"layer":null,"private":false,"declares":[],
947
1165
  "changedSince":{"instance":"release-manager-v2","was":"<old oid>"}},
948
1166
  {"name":"oats.okf","from":{"kind":"package","package":"oats.okf","version":"2.1.3","commit":"<oid>","integrity":"sha256-…","repoKey":"github.com/awebai/oats-okf"},
949
- "layer":"knowledge","private":false,"changedSince":false}],
1167
+ "layer":"knowledge","private":false,"declares":["bindings-file","git-timeout","harvest-model","harvest-runtime","state-dir"],"changedSince":false}],
950
1168
  "team":"engineering",
951
1169
  "resolution":"6e3050c0d005879441ab017d",
952
1170
  "workspace":"github.com/acme/agents",
@@ -956,13 +1174,21 @@ between preview and apply is `E_DECISION_STALE`):
956
1174
  ```
957
1175
 
958
1176
  - `modules[].from` is exactly the `from` recorded in `instance.json` on apply.
1177
+ - `modules[].declares`: the manifest's declared setting keys, as in `inspect`
1178
+ (feature `settings-declared`).
959
1179
  - `changedSince`: `null` (no previous instance of this soul), `false`
960
1180
  (unchanged since the newest previous instance), or
961
1181
  `{ instance, was }` (`was` = the previous commit, or `null` when the previous
962
1182
  instance had no such module).
963
- - `capabilities[]` / `skills[]` keep their Preview-1 meaning; on a workspace
964
- spawn the authoritative module set is `modules[]` (`capabilities[]` may be
965
- empty there, since capability rows are filled after materialization).
1183
+ - `capabilities[]` / `skills[]` on a **workspace** spawn are **objects**, not
1184
+ Preview-1's strings: `capabilities[]` is `{ name, origin }` (`origin` =
1185
+ `package:<id>@<version>` or `member:<repoKey>@<commit>`), and `skills[]` is
1186
+ `{ name, source }`. Neither is a binding surface, because the authoritative
1187
+ module set is `modules[]` and what apply binds is `decision.effective` /
1188
+ `decision.resolution`. Consumers should read those fields, not project
1189
+ `capabilities[]` / `skills[]`. (Corrected 2026-09-24: this said "keep their
1190
+ Preview-1 meaning", which read as strings; found by the Desktop engineer in
1191
+ F3.)
966
1192
  - `workspace` is the workspace host's canonical key, `team` the soul's label
967
1193
  (or `null`), `resolution` the 24-hex revision `decision.resolution` binds.
968
1194
  - `--provider <cap> <key>=<value>` (repeatable; `a.b=c` nests) is accepted by
@@ -976,7 +1202,7 @@ between preview and apply is `E_DECISION_STALE`):
976
1202
  members or externals), `E_SOUL_AMBIGUOUS { name, repos[] }` (name it
977
1203
  `<repo>/<soul>`). Resolution errors keep their codes (`E_NOT_A_MEMBER`,
978
1204
  `E_MEMBERSHIP_UNCONFIRMED { repoKey, reason }`, `E_CAPABILITY_MISSING { hint? }`,
979
- `E_CAPABILITY_PRIVATE`, `E_PACKAGE_MISSING`, `E_PACKAGE_UNAPPROVED { id, version, commit }`,
1205
+ `E_CAPABILITY_PRIVATE`, `E_PACKAGE_MISSING`, `E_PACKAGE_INTEGRITY { why: "capabilities", listed, locked }`,
980
1206
  `E_SLOT_CONFLICT { slot, modules[], reason? }`, `E_SKILL_DUPLICATE { name, modules[] }`,
981
1207
  `E_COMPATIBILITY { capability, package, version, range, why? }`).
982
1208
 
@@ -995,11 +1221,18 @@ Written by materialization inside the spawn transaction; read back by
995
1221
  "oats.okf":{"from":{"kind":"package","package":"oats.okf","version":"2.1.3","commit":"<oid>","integrity":"sha256-…","repoKey":"github.com/awebai/oats-okf"},
996
1222
  "commit":"<oid>","digest":"sha256-…","materializedAt":"<iso>"}},
997
1223
  "providers":{"acme-release-tooling":{},"oats.okf":{"owns":"release-manager","reads":["platform-engineer"],"state-dir":"/Users/ana/.oats/okf"}},
998
- "workspace":{"key":"github.com/acme/agents","commit":"<oid>","resolution":"<24 hex>","standalone":false,
1224
+ "workspace":{"key":"github.com/acme/agents","name":"acme","deployment":"/Users/ana/acme-workspace","commit":"<oid>","resolution":"<24 hex>","standalone":false,
999
1225
  "soul":{"repoKey":"github.com/acme/agents","commit":"<oid>","team":"engineering"}},
1000
1226
  "capabilities":[{"id":"oats.okf","capability":"oats.okf","origin":"package:oats.okf@2.1.3","…":"one row per module"}]}
1001
1227
  ```
1002
1228
 
1229
+ `workspace.name` (the workspace file's `name`) and `workspace.deployment` (the
1230
+ directory holding the deployment's `oats-local.yaml`) are recorded from 0.26.0,
1231
+ so a home answers them without discovery: `oats inspect --home` reports the
1232
+ recorded name, and `oats operation run --home` hands it to the provider as
1233
+ `OATS_WORKSPACE_NAME`. Homes spawned
1234
+ before 0.26.0 lack both; the name is then discovered, or `null`.
1235
+
1003
1236
  `workspace.standalone` is `true` when the instance was spawned from the
1004
1237
  **standalone view** (decisions 10/25: a *member* whose workspace could not be
1005
1238
  read — `workspace.key` is then the member repo's key, and `modules` holds the
@@ -1008,9 +1241,14 @@ spawn. The same view is marked `standalone: true` in `oats sync --json` (with
1008
1241
  `workspace.name` = `standalone:<repo>`) and in the roster. `capabilities[]` is
1009
1242
  the per-module row set (`toCapabilityRows`) that `oats inspect`/`status` read.
1010
1243
 
1244
+ `soulDir` (0.26.0) is the absolute soul directory the instance incarnates — a
1245
+ workspace soul's per-commit copy `<deployment>/agents/<soul>/souls/<commit12>`, or
1246
+ the read-only soul inside a capability package — and is what every classic
1247
+ lifecycle hook and dispatched command receives as `OATS_SOUL`. Instance homes carry no `soul` link.
1248
+
1011
1249
  `digest` is the sha256 of the copied module tree (`<home>/.oats/modules/<cap>/`);
1012
- `providers.<cap>` is the merged payload (soul ⊕ `oats-local.yaml`
1013
- `settings.<cap>` ⊕ `--provider`), `{}` for a capability with none. Copies live at
1250
+ `providers.<cap>` is the merged payload (manifest defaults ⊕ soul ⊕
1251
+ `oats-local.yaml` `settings.<cap>` ⊕ `--provider`), `{}` for a capability with none. Copies live at
1014
1252
  `<home>/.oats/modules/<cap>/` and `<home>/.agents/skills/<cap>/<skill>/`.
1015
1253
 
1016
1254
  ### `oats status [--dir] --json` — module drift
@@ -1041,54 +1279,59 @@ top-level `workspace` reachability field:
1041
1279
  - Text mode prints `modules: <cap> from <member|package …> @ <7-char>` lines
1042
1280
  for non-current modules (`--verbose` for all).
1043
1281
 
1282
+ ### Eligible teams (feature `teams`, OATS 0.26.0)
1283
+
1284
+ A soul's `team` may be a list of labels; the first is the primary
1285
+ ([teams contract](design/2026-09-25-teams-contract.md)). Every label is an
1286
+ **eligible** team — which the messaging provider may join on an explicit
1287
+ request (a spawn provider setting, or its own join/leave verbs); the kernel
1288
+ joins nothing. One entry per label, in soul order:
1289
+
1290
+ ```json
1291
+ {"label":"engineering","team":"aweb:acme.eng","mapped":true,"payload":{"private":"per-human","team":"aweb:acme.eng"}}
1292
+ {"label":"reviewers","team":null,"mapped":false,"payload":{"private":"per-human"}}
1293
+ ```
1294
+
1295
+ `payload` = `workspace.messaging` ⊕ `byTeam[label]` (base alone when unmapped);
1296
+ `team` = the mapped payload's team id, else `null`. No label → `[]`.
1297
+
1298
+ Where it appears:
1299
+ - `oats spawn … --preview --json`: top-level `teams` (next to `team`, which
1300
+ stays the primary label). `settings.<messaging>` stays the primary's merged
1301
+ payload and never carries `teams`.
1302
+ - `oats inspect --soul|--home --json`: top-level `teams` and `teamsSource`.
1303
+ For `--home` the teams are **live** (the soul's labels and the workspace's
1304
+ `messaging` as the deployment resolves them now, in two repository reads;
1305
+ the home's modules are unchanged): `teamsSource: "live"`, or `"recorded"`
1306
+ with the spawn-time list when the workspace cannot be read now. Providers
1307
+ get the same marker as `OATS_TEAMS_SOURCE`.
1308
+ - `instance.json`: `teams` (the spawn-time list, kept as evidence; never
1309
+ rewritten) and `workspace.soul.labels`.
1310
+ - `oats souls --json`: each row carries `labels` (`team` stays the primary).
1311
+ - A spawn, preview or `inspect --soul` whose labels give one capability
1312
+ different `defaults.byTeam` entries answers `E_TEAM_CONFLICT { capability,
1313
+ labels: [a, b], entries, paths }`.
1314
+
1044
1315
  ### Probe
1045
1316
 
1046
1317
  ```json
1047
- {"…":"…","features":["…","workspace-v2","instance-modules","spawn-provider-payload"],"workspaceApi":2}
1318
+ {"…":"…","features":["…","workspace-v2","instance-modules","spawn-provider-payload","packages-no-approval","spawn-name","settings-origins","teams"],"workspaceApi":2}
1048
1319
  ```
1049
1320
 
1050
1321
  A feature is listed only once the binary implements it. Gate `sync`/`package`/
1051
1322
  `workspace status`/`capabilities`/`souls` on `workspace-v2`; gate reading
1052
1323
  `instance.json.modules` and preview `modules[]` on `instance-modules`; gate
1053
- `--provider` on `spawn-provider-payload`.
1054
-
1055
- ## Instruction refresh (`oats session recompose`, feature `session-recompose`, OATS 0.24.8+)
1056
-
1057
- A live instance's composed `AGENTS.md` is generated at spawn and outranks any
1058
- mail or tracked file *in the running context*. When a soul changes (a role or
1059
- budget amendment) and a respawn is not possible or wanted, an operator
1060
- refreshes the home in place:
1061
-
1062
- - `oats session recompose --home <abs> [--dry-run] --json` → `{home, instance,
1063
- agent, soulDir, contextDir, changed, dryRun, blocks[{source,file}], previous,
1064
- note}`. Same composer spawn used, the home's own `soul` link and recorded
1065
- context/work mode. `changed:false` is a no-op (no receipt). On change the
1066
- prior text is retained as `previous` (`<home>/.oats-agents-md.<stamp>.previous`),
1067
- `instance.json` gains `instructions[]`/`recomposedAt`, and a `recomposed`
1068
- event is appended.
1069
- - **Nothing is signalled or restarted** — the harness re-reads on its own
1070
- schedule; the receipt's `note` says so. Refuses a retiring home
1071
- (`E_INSTANCE_RETIRING`), captured incarnations and capability-defined souls
1072
- (`E_UNSUPPORTED_MODE`: those are refreshed by a new resolution / package).
1073
- - **Module homes (0.25+) are `E_UNSUPPORTED_MODE` too.** A home whose
1074
- `instance.json` carries `modules{}` (spawned on a workspace deployment,
1075
- `instance-modules`) answers `E_UNSUPPORTED_MODE` ("recompose from
1076
- materialized modules is not supported yet; re-spawn"): its `AGENTS.md` was
1077
- composed from the soul at a recorded member commit plus the materialized
1078
- modules' injects, and the instance never changes under itself (decision 7).
1079
- **Desktop contract (Phase F, F4)**: for a module home, show the drift rows
1080
- and offer *re-spawn* (preview → apply of the same soul/purpose, then retire
1081
- the old instance); do not offer "recompose". A kernel recompose for module
1082
- homes is not planned — an instance never changes under itself.
1083
- The refresh path for such a home is a new spawn (the soul is re-fetched at
1084
- the member's current commit). `session-recompose` **stays advertised** in
1085
- `features[]` because the verb still works for classic homes; gate the UI
1086
- action on the feature AND on the absence of `instance.json.modules`
1087
- (`oats status --json` `instances[].modules` is non-empty for a module home),
1088
- and render the typed refusal otherwise.
1089
- - Gate on `features.includes("session-recompose")`. It is an **operator
1090
- action** (the human or the instance's parent), never something a Desktop
1091
- poll or an agent runs on itself.
1324
+ `--provider` on `spawn-provider-payload`; gate reading `teams`, `teamsSource`,
1325
+ `labels` and `warnings[]` on `teams`; gate reading `declares` on
1326
+ `settings-declared`.
1327
+
1328
+ ## Instruction refresh (`oats session recompose`) — removed in 0.26.0
1329
+
1330
+ `oats session recompose` answers `E_UNKNOWN_COMMAND`, and the
1331
+ `session-recompose` feature is no longer advertised. An instance never changes
1332
+ under itself: the refresh path is a re-spawn (preview → apply of the same
1333
+ soul/purpose, then retire the old instance), which fetches the soul at the
1334
+ member's current commit.
1092
1335
 
1093
1336
  ## Mutations exposed to Desktop v1
1094
1337
 
@@ -1172,13 +1415,52 @@ Additional informative fields: `repo`, `runtime`, `model`, `parent`,
1172
1415
  was declared, else null), `relation` (`child`/`sibling`/`parent` when a
1173
1416
  relation was declared at spawn, else null), `spawnOrigin`, `attach`.
1174
1417
 
1175
- Stable error codes: `E_USAGE`, `E_NO_DEPLOYMENT`, `E_UNKNOWN_AGENT`,
1418
+ Stable error codes: `E_USAGE`, `E_LOCAL_MISSING` (no `oats-local.yaml` in reach:
1419
+ a spawn needs a workspace deployment), `E_NO_DEPLOYMENT` (the deployment's
1420
+ `agents/` root is missing), `E_SOUL_UNKNOWN`, `E_UNKNOWN_AGENT`,
1176
1421
  `E_AMBIGUOUS_SOUL`, `E_PARENT_NOT_FOUND`, `E_RELATIVE_NOT_FOUND`,
1177
1422
  `E_RELATIVE_AMBIGUOUS` (a `--relative-to`/`--parent` anchor name matches
1178
1423
  multiple team instances — disambiguate with `--relative-root <agents-root>`
1179
1424
  — or the chosen anchor is shadowed by a same-named instance so the lineage
1180
1425
  edge would resolve wrongly), `E_BAD_ARGS`,
1181
- `E_SPAWN_FAILED`.
1426
+ `E_INSTANCE_NAME_INVALID`, `E_INSTANCE_NAME_TAKEN`, `E_SPAWN_FAILED`.
1427
+
1428
+ **Instance names** (0.26.0, feature `spawn-name`). By default the name is
1429
+ derived: `<agent>-<purpose>` with `--purpose <slug>`, else `<agent>-<n>`.
1430
+ `--name <slug>` (human decision 2026-09-24) is the explicit opt-in: the
1431
+ instance name is **exactly** `<slug>`, with no `<agent>-` prefix.
1432
+
1433
+ - `--name` and `--purpose` are mutually exclusive (`E_BAD_ARGS`), and
1434
+ `--name` needs a value (`E_BAD_ARGS`).
1435
+ - The name is never rewritten. Input that is not already a slug (lowercase
1436
+ letters and digits, single dashes between them) is
1437
+ `E_INSTANCE_NAME_INVALID`, and so is a name equal to any soul name of the
1438
+ deployment (souls on the agents root, and every soul the workspace
1439
+ declares, fetched or not). Soul and instance references stay unambiguous.
1440
+ - **Instance names are at most 64 characters** (0.26.0; the tightest
1441
+ consumer is the messaging alias, which allows 1–64). This covers every
1442
+ name, explicit and derived. A longer name is `E_INSTANCE_NAME_INVALID`
1443
+ ("instance names are at most 64 characters"), in preview and apply alike,
1444
+ and is never truncated. For a derived name the refusal names the purpose
1445
+ to shorten, and the de-duplication suffix counts: when `<agent>-<purpose>`
1446
+ is taken and `<agent>-<purpose>-2` would exceed 64, the spawn is refused.
1447
+ - **Names are unique across the deployment.** An explicit name that any
1448
+ `<agents-root>/<soul>/instances/` already holds (including homes whose soul
1449
+ was since removed), or that a live window in the target tmux session carries
1450
+ (tmux backend, launched or `--no-launch`), is `E_INSTANCE_NAME_TAKEN`
1451
+ (`details.instance`, `details.home` or `details.session`). There is never a
1452
+ silent `-2` for a name the operator typed. These checks run after
1453
+ idempotency-key recovery (a keyed retry replays its receipt), and a
1454
+ concurrent spawn of another soul under the same name is caught after
1455
+ placement (see *Exclusive placement*). The invariant covers spawns through
1456
+ the CLI. Homes from earlier kernels may already share a name.
1457
+ - Derived names de-duplicate deployment-wide too (`-2`, `-3`, …), against
1458
+ every soul's instances and every soul name. Two souls never derive the same
1459
+ name (soul `a` with `--purpose b-c` against soul `a-b` with `--purpose c`).
1460
+ - `--preview` reports the final name (`instance`, `decision.instance`) and
1461
+ refuses with the same codes. The name is part of the decision revision, so
1462
+ `--expect-decision` binds it: another name under a confirmed decision is
1463
+ `E_DECISION_STALE`.
1182
1464
 
1183
1465
  Dispatch-level failures (any `--json` command): `E_UNKNOWN_COMMAND` (no
1184
1466
  kernel subcommand or capability namespace matches, or unknown capability