@awebai/oats 0.25.9 → 0.27.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 (183) hide show
  1. package/README.md +8 -6
  2. package/bin/oats.mjs +648 -1755
  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 +279 -93
  8. package/capabilities/oats-aweb/injects/aweb.md +7 -2
  9. package/capabilities/oats-aweb/lib/binding-wire.mjs +89 -13
  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 +8 -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-review/oats.json +3 -2
  20. package/docs/capabilities.md +229 -58
  21. package/docs/capability-manifest.schema.json +29 -9
  22. package/docs/configuration.md +17 -5
  23. package/docs/conventions.md +18 -28
  24. package/docs/design/2026-09-08-expert-assisted-deployment-proposal.md +1 -1
  25. package/docs/design/2026-09-13-knowledge-and-memory-direction.md +3 -3
  26. package/docs/design/2026-09-14-portable-souls-and-git-workspaces.md +2 -2
  27. package/docs/design/2026-09-15-portable-souls-handoff.md +2 -2
  28. package/docs/design/2026-09-15-portable-souls-implementation.md +1 -1
  29. package/docs/design/2026-09-20-redesign-program-board.md +2 -2
  30. package/docs/design/2026-09-23-workspace-module-contracts.md +1 -1
  31. package/docs/design/2026-09-23-workspace-v2-implementation-plan.md +1 -1
  32. package/docs/design/2026-09-24-desktop-phase-f-boundary.md +34 -1
  33. package/docs/design/2026-09-24-phase-d-plan.md +57 -0
  34. package/docs/design/2026-09-25-teams-contract.md +226 -0
  35. package/docs/design/README.md +3 -3
  36. package/docs/design/launch-configurations.md +20 -16
  37. package/docs/design/operations-contract.md +27 -10
  38. package/docs/desktop-cli-api.md +604 -271
  39. package/docs/desktop-instance-start.md +3 -3
  40. package/docs/desktop.md +7 -13
  41. package/docs/execution-targets.md +16 -18
  42. package/docs/first-team.md +15 -18
  43. package/docs/implementation.md +31 -62
  44. package/docs/integrations.md +64 -33
  45. package/docs/knowledge-capability-authoring.md +1 -1
  46. package/docs/knowledge-reference/package-craft.md +10 -8
  47. package/docs/knowledge-theory.md +1 -1
  48. package/docs/knowledge.md +10 -11
  49. package/docs/layers.md +16 -17
  50. package/docs/oats-local.schema.json +30 -1
  51. package/docs/oats-membership.schema.json +5 -3
  52. package/docs/oats-package.schema.json +2 -2
  53. package/docs/oats-workspace.schema.json +1 -1
  54. package/docs/{official-marketplace.md → official-catalog.md} +15 -16
  55. package/docs/packages.md +76 -53
  56. package/docs/release-notes/v0.22.0.md +1 -1
  57. package/docs/release-notes/v0.23.1.md +1 -1
  58. package/docs/release-notes/v0.26.0.md +670 -0
  59. package/docs/release-notes/v0.27.0.md +100 -0
  60. package/docs/schedules.md +54 -132
  61. package/docs/servers.md +4 -4
  62. package/docs/soul.schema.json +11 -4
  63. package/docs/souls-and-instances.md +60 -47
  64. package/docs/workspaces.md +80 -58
  65. package/injects/instance-boundary.md +2 -2
  66. package/injects/work-attached.md +1 -1
  67. package/injects/work-workspace.md +2 -2
  68. package/lib/{portable-files.mjs → bounded-read.mjs} +6 -6
  69. package/lib/{portable-values.mjs → canonical-json.mjs} +3 -12
  70. package/lib/capability-contract.mjs +110 -0
  71. package/lib/config-data.mjs +2 -2
  72. package/lib/core.mjs +947 -5023
  73. package/lib/deprecation.mjs +24 -0
  74. package/lib/digest.mjs +12 -0
  75. package/lib/instance-inspect.mjs +397 -0
  76. package/lib/instance-lifecycle.mjs +3 -4
  77. package/lib/instance-resolution.mjs +212 -26
  78. package/lib/instruction-composition.mjs +0 -20
  79. package/lib/materialize.mjs +6 -4
  80. package/lib/operator-dispatch.mjs +33 -13
  81. package/lib/packages.mjs +25 -190
  82. package/lib/process-group.mjs +1 -1
  83. package/lib/provider-binding.mjs +4 -2
  84. package/lib/provider-reasons.mjs +3 -68
  85. package/lib/remote.mjs +1 -1
  86. package/lib/resolve.mjs +204 -68
  87. package/lib/schedule.mjs +136 -292
  88. package/lib/servers.mjs +70 -38
  89. package/lib/{portable-shape.mjs → shape.mjs} +4 -3
  90. package/lib/tree-copy.mjs +44 -0
  91. package/lib/workspace.mjs +132 -20
  92. package/package-catalog.json +6 -6
  93. package/package.json +1 -1
  94. package/packages/record/lib/session-roots.mjs +8 -6
  95. package/skills/integration-authoring/SKILL.md +48 -40
  96. package/skills/oats-getting-started/SKILL.md +105 -110
  97. package/skills/oats-support/SKILL.md +2 -2
  98. package/skills/soul-craft/SKILL.md +13 -6
  99. package/bin/oats-pi-sdk-host.mjs +0 -17
  100. package/docs/2026-09-03-architecture-proposal.md +0 -642
  101. package/docs/artifact-approvals.schema.json +0 -7
  102. package/docs/captured-invocation-context.schema.json +0 -7
  103. package/docs/captured-resolution.schema.json +0 -7
  104. package/docs/design/package-engine-contract.md +0 -813
  105. package/docs/design/package-runtime-api.md +0 -588
  106. package/docs/desktop-succession.md +0 -57
  107. package/docs/execution-capsule.schema.json +0 -108
  108. package/docs/first-team-demo.md +0 -92
  109. package/docs/knowledge-migration.md +0 -147
  110. package/docs/migration-from-oas.md +0 -103
  111. package/docs/oats-config.schema.json +0 -172
  112. package/docs/oats-lock-v3.schema.json +0 -7
  113. package/docs/oats-lock.schema.json +0 -175
  114. package/docs/operating-team-migration.md +0 -470
  115. package/docs/portable.schema.json +0 -2512
  116. package/docs/provider-check-input.schema.json +0 -7
  117. package/docs/rebuild-to-v2.md +0 -511
  118. package/docs/workspace-adoption.md +0 -74
  119. package/injects/framework-workspace.md +0 -7
  120. package/injects/local-soul.md +0 -19
  121. package/injects/oats-portable.md +0 -20
  122. package/injects/oats.md +0 -11
  123. package/injects/portable-instance-boundary.md +0 -39
  124. package/injects/portable-work-directory.md +0 -29
  125. package/lib/artifact-approvals.mjs +0 -120
  126. package/lib/artifact-tree.mjs +0 -141
  127. package/lib/capability-artifacts.mjs +0 -179
  128. package/lib/capability-execution.mjs +0 -15
  129. package/lib/capability-inputs.mjs +0 -39
  130. package/lib/capability-provenance.mjs +0 -231
  131. package/lib/captured-action-shape.mjs +0 -21
  132. package/lib/captured-admission-shape.mjs +0 -20
  133. package/lib/captured-binding-file.mjs +0 -36
  134. package/lib/captured-dispatch.mjs +0 -66
  135. package/lib/captured-instance-index.mjs +0 -277
  136. package/lib/captured-invocation-context.mjs +0 -130
  137. package/lib/captured-launch-request.mjs +0 -66
  138. package/lib/captured-operation-process.mjs +0 -15
  139. package/lib/captured-pi-custody.mjs +0 -29
  140. package/lib/captured-pi-host.mjs +0 -167
  141. package/lib/captured-pi-outcome.mjs +0 -172
  142. package/lib/captured-resolutions.mjs +0 -275
  143. package/lib/captured-scaffold.mjs +0 -87
  144. package/lib/captured-selector.mjs +0 -28
  145. package/lib/captured-session-backend.mjs +0 -52
  146. package/lib/captured-source-receipt-file.mjs +0 -72
  147. package/lib/helper-injection-policy.mjs +0 -104
  148. package/lib/legacy-lock-codec.mjs +0 -106
  149. package/lib/manifest-settings.mjs +0 -84
  150. package/lib/package-closure.mjs +0 -48
  151. package/lib/package-materialization.mjs +0 -83
  152. package/lib/pi-sdk-host.mjs +0 -229
  153. package/lib/portable-artifacts.mjs +0 -115
  154. package/lib/portable-choices.mjs +0 -82
  155. package/lib/portable-composition.mjs +0 -136
  156. package/lib/portable-digest.mjs +0 -105
  157. package/lib/portable-identity.mjs +0 -40
  158. package/lib/portable-lock.mjs +0 -117
  159. package/lib/portable-onboarding-request.mjs +0 -49
  160. package/lib/portable-onboarding.mjs +0 -256
  161. package/lib/portable-package-preparation.mjs +0 -188
  162. package/lib/portable-policy.mjs +0 -44
  163. package/lib/portable-soul.mjs +0 -42
  164. package/lib/portable-state.mjs +0 -80
  165. package/lib/prepare-composition.mjs +0 -170
  166. package/lib/prepared-bindings.mjs +0 -92
  167. package/lib/prepared-resources.mjs +0 -127
  168. package/lib/provider-binding-broker.mjs +0 -65
  169. package/lib/provider-binding-wire.mjs +0 -116
  170. package/lib/readiness.mjs +0 -225
  171. package/lib/repository-observation.mjs +0 -226
  172. package/lib/resolution-shape.mjs +0 -393
  173. package/lib/schedule-capsule.mjs +0 -206
  174. package/lib/soul-constraints.mjs +0 -40
  175. package/lib/source-projection.mjs +0 -84
  176. package/lib/source-spec.mjs +0 -189
  177. package/lib/workspace-definition.mjs +0 -126
  178. package/lib/workspace-discovery.mjs +0 -146
  179. package/skills/oats/SKILL.md +0 -162
  180. package/skills/oats-config/SKILL.md +0 -164
  181. package/skills/oats-packages/SKILL.md +0 -184
  182. package/skills/oats-portable/SKILL.md +0 -115
  183. package/skills/oats-portable-artifacts/SKILL.md +0 -63
@@ -18,15 +18,17 @@ 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
 
27
29
  Optional features are negotiated from the probe's `features` array. Starting
28
30
  an existing home requires `session-start`; named launch configurations and
29
- runtime/permission overrides require `launch-config`; restarting a running
31
+ harness/permission overrides require `launch-config`; restarting a running
30
32
  home also requires `session-restart`. Desktop checks the corresponding
31
33
  `remote` entries before offering these operations for a server. The router
32
34
  then probes the execution host before sending a mutation. An absent feature
@@ -46,49 +48,405 @@ 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
+ Either may also carry `"warnings":[…]` (0.27.0+), present only when there is
52
+ something to say. The one warning so far is `deprecated-runtime-name` (see
53
+ [the harness rename](#the-harness-rename-feature-harness-oats-0270)). `oats status
54
+ --json`, whose document is not an envelope, carries the same `warnings` beside
55
+ its `problems`. In text mode the warning is one `oats: warning: …` line on stderr.
56
+
57
+ ## The harness rename (feature `harness`, OATS 0.27.0)
58
+
59
+ What starts an instance (pi, claude or codex) is its **harness**. 0.27.0 renames
60
+ the kernel's `runtime` to `harness` everywhere it means that:
61
+
62
+ - Outputs speak only the new names.
63
+ - Every input written before 0.27.0 still works. The rule is read either,
64
+ write new: the old spelling is read as the new one, and the next write
65
+ records the new one.
66
+ - A command that read an old spelling answers **one** warning:
67
+ `{"code":"deprecated-runtime-name","key":"runtime","replacement":"harness","sources":[…],"message":"…"}`.
68
+ `sources` names each place it read the old spelling (a flag, a file and its
69
+ key, a home).
70
+ - A pair that disagrees is refused rather than guessed, for example
71
+ `--harness pi --runtime claude`, or both keys with different values.
72
+ - A later release drops the old spellings.
73
+
74
+ Gate on the feature `harness`. A kernel without it speaks the old names: the
75
+ routed commands (`--server`) already translate for such a host, sending
76
+ `--runtime` and `runtime` keys to it and reading its `runtimes` list.
77
+
78
+ | Surface | Before 0.27.0 | 0.27.0 | Old spelling still accepted? |
79
+ |---|---|---|---|
80
+ | `oats version --json` | `runtimes: [pi, claude, codex]` | `harnesses: [...]`, feature `harness` | **Dropped**: no `runtimes` alias; gate on the feature |
81
+ | Flag on `spawn` (and `--preview`), `session start`/`restart`, `launch-config preview`, and their `--server` forms | `--runtime <h>` | `--harness <h>` | Yes, with the warning (the okf 2.1.5 harvest worker passes `--runtime` to spawn). Both flags disagreeing → `E_BAD_ARGS` |
82
+ | `oats status --json`: `agents[]` rows (soul default) and `agents[].instances[]` rows | `runtime` | `harness` | Output only |
83
+ | `oats status --json`: instance rows' `composition.materialized` | `runtimePackages`, `runtimePosture` | `harnessPackages`, `harnessPosture` | Output only |
84
+ | `oats inspect --json` `souls[]` rows, remote roster rows | `runtime` | `harness` | Output only; a pre-0.27 host's `runtime` rows are read as `harness` |
85
+ | `oats inspect --home --json` `instance` | `runtime` | `harness` | Output only |
86
+ | The launch plan's package check (`launch-config preview` `problems[]`) | `runtime-packages` | `harness-packages` | Output only |
87
+ | Soul `soul.yaml` (member souls and capability-defined agents) | `runtime:` | `harness:` | Yes, **without** a warning: released capabilities (oats.aweb 1.13.1) ship `runtime:`, and the operator cannot fix a provider's file. Both, disagreeing → `E_BAD_MANIFEST` |
88
+ | `oats-local.yaml` `launch-configs.<name>` | `runtime:` | `harness:` | Yes, with the warning. Both, disagreeing → `E_WORKSPACE_SCHEMA`. `launch-config set` writes `harness` (a `runtime` in its `--file` definition too) |
89
+ | `launch-config list/preview --json` rows and `selection` | `runtime` | `harness` | Output only |
90
+ | Home `instance.json` | `runtime`; launch recipe `launch` version 1 `{runtime}` | `harness`; recipe version **2** `{harness}` | Yes, with the warning naming the home: a 0.26.0 home inspects, starts, restarts and retires; its next start or restart records the new names |
91
+ | `oats spawn … --preview` decision | `effective.runtime` | `effective.harness` | Output only. The revision digests the key names, so a decision previewed by a 0.26 kernel is `E_DECISION_STALE` at apply (with the fresh decision) |
92
+ | `spawn`/`session` results, `spawned` event `data` | `runtime` | `harness` | Output only |
93
+ | Schedule definitions (`schedule add/update --spec-json`, stored jobs, `schedule list`) | `runtime` | `harness` | Yes, with the warning. A stored job is read in the new name and saved in it next time. Both, disagreeing → `E_SCHEDULE_INVALID` (a stored job: invalid on its own) |
94
+ | Schedule run records (`lastRun`, `recentRuns`) | `startedRuntime` | `startedHarness` | Output; a 0.26.0 record's `startedRuntime` is still read |
95
+ | Error codes | `E_UNSUPPORTED_RUNTIME`, `E_RUNTIME_PACKAGE`, `E_RUNTIME_RESOURCE_MISSING` | `E_UNSUPPORTED_HARNESS`, `E_HARNESS_PACKAGE`, `E_HARNESS_RESOURCE_MISSING` | Output only |
96
+ | Hook environment (spawn and launch hooks) | `OATS_RUNTIME`, `OATS_PREVIOUS_RUNTIME` | `OATS_HARNESS`, `OATS_PREVIOUS_HARNESS` | Both are set, with the same values, for released hooks |
97
+ | Capability manifest `requires[]` harness package | `runtime` | `harness` | Yes, **without** a warning (a provider's file); a row naming both is refused |
98
+ | Package verification `loadedBy` | `runtime-discovery` | `harness-discovery` | Output only |
99
+
100
+ Unchanged, because they do not name the harness: the session endpoint
101
+ vocabulary (`runtimeAuthority`, `runtimeState`/`runtimeError` in liveness,
102
+ `E_RUNTIME_ENDPOINT_UNKNOWN`, `E_RUNTIME_AUTHORITY_MISMATCH`,
103
+ `E_RUNTIME_QUIESCE_FAILED`); `capabilityRuntime`; the retirement baseline's
104
+ `runtime`; oats.okf's `harvest-runtime` setting; and the kernel's
105
+ "runtime-neutral" design. Hook stdin carries no `launch.runtime` (no 0.26 hook
106
+ emitter wrote it).
107
+
108
+ ## Inspect, readiness and operation run on the workspace model (`operationsApi: 2`, `soulsApi: 2`, `readinessApi: 2`, OATS 0.26.0)
109
+
110
+ On a workspace deployment (an `oats-local.yaml` in reach of `--dir`), and for
111
+ any home whose `instance.json` records `modules`, these three commands read the
112
+ workspace model's own records and **never the classic config chain**. The probe
113
+ integers are the gate; there is no feature string. The probe's integer says
114
+ this kernel CAN answer the v2 shape. **Dispatch on the payload's own integer**.
115
+ There is no v1 shape any more (0.26.0 removed the classic chain's answers):
116
+ with no `oats-local.yaml` in reach and no `--home`, the three commands answer
117
+ `E_LOCAL_MISSING`; a `--home` whose `instance.json` records no `modules`
118
+ (spawned by an earlier kernel) answers `E_UNSUPPORTED_MODE` (re-spawn it from
119
+ the deployment); an unreadable `--home` answers `E_SESSION_UNKNOWN`.
120
+
121
+ **The captured/portable path was removed in 0.26.** A *captured home* (its `instance.json`
122
+ records `executionBinding`, `incarnationId` or `captured`: spawned through
123
+ 0.24–0.25's `prepare` / `--deployment --resolution`) answers `E_UNSUPPORTED_MODE`
124
+ (`details: {home, captured: true}`) to these three commands, to `session
125
+ start|restart` and to its in-home commands; `oats retire` still works on it, and
126
+ its result's `warnings[]` names each capability whose retire hook did NOT run
127
+ (what it created is not revoked). `oats status --json` / `oats doctor --json`
128
+ name captured homes once, in `problems[]`, as `legacy-captured-home` `{instances,
129
+ homes, message}`. The captured selectors `--deployment`, `--resolution` and
130
+ `--artifact-set`, and an inherited `OATS_DEPLOYMENT`/`OATS_RESOLUTION`, are refused
131
+ by every command except `version` (`E_UNSUPPORTED_MODE`, `details.selector` or
132
+ `details.inherited`); `oats prepare` and `oats inspect --request` are removed
133
+ verbs (`E_UNKNOWN_COMMAND`, `details: {removed, replacement}`). The version
134
+ document no longer carries `capturedDispatchApi` / `capturedDispatchActions`.
135
+
136
+ | Command | Integer (probe and payload) | 0.25.x value |
137
+ |---|---|---|
138
+ | `oats inspect --json` | `operationsApi: 2` (top level); each `souls[]` row `soulsApi: 2` | 1 / 1 |
139
+ | `oats readiness --json` | `readinessApi: 2` | 1 |
140
+ | `oats operation run --json` | `operationsApi: 2` on the result | absent |
141
+
142
+ The probe's `soulsApi` follows the inspect soul rows. The `oats souls --json`
143
+ document keeps its own `soulsApi: 1`, because its shape did not change (see
144
+ [`oats souls`](#oats-capabilities---dir---json-capabilitiesapi-1-oats-souls---dir---json-soulsapi-1)).
145
+
146
+ **The subject is an instance or a soul, never a scope.** Pass `--home <abs>`
147
+ or `--soul <name>`. A workspace deployment with neither is `E_BAD_ARGS`. An
148
+ `oats-local.yaml` that exists but cannot be read is reported with its own
149
+ error code, never answered from the classic chain. For
150
+ inspect, the message points to `oats souls` and `oats capabilities`, the
151
+ scope-wide lists.
152
+ - `--home` selects the instance, from its `instance.json` and the module copies
153
+ under `<home>/.oats/modules/`. Everything is as spawned.
154
+ - `--soul` selects the soul, resolved exactly as a spawn of it would be:
155
+ discovery, then soul `capabilities:` plus workspace defaults, then the lock.
156
+ - A v2 home lives at `<deployment>/agents/<soul>/instances/<name>`, and its
157
+ deployment is derived from that path. `<deployment>/oats-local.yaml` must
158
+ exist exactly there (never found by walking up), otherwise
159
+ `E_HOME_MISMATCH`. A v2 spawn ignores an ambient `PI_AGENTS_ROOT` /
160
+ `OATS_ROOT`, so its homes always have this layout.
161
+ - `--dir`, if given with `--home`, must be that home's deployment
162
+ (`E_HOME_MISMATCH`). `--agents-root`, if given, must be
163
+ `<deployment>/agents` (`E_HOME_MISMATCH` with `--home`, `E_SOUL_UNKNOWN`
164
+ with `--soul`).
165
+
166
+ **Gone from every payload:** `scope` (`context`, `chain`, `team`,
167
+ `agentsRoots`), config `levels`, `activation {declaredAt, target, level,
168
+ source}`, `currentConfig`, `snapshot.drift`, `health {trusted, approved,
169
+ locked, installedIntegrity}`, soul `provenance`/`readiness`, and the scope's
170
+ portable `sources`. A capability's origin is its module's `from` (member
171
+ commit, or package version + commit + integrity). Its settings are the merged
172
+ payload the spawn recorded for a home, or the resolution computes for a soul.
173
+
174
+ ### `oats inspect (--home <abs> | --soul <name> [--dir <d>]) --json` → `operationsApi: 2`
79
175
 
80
176
  ```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"]}]}
177
+ {"operationsApi":2,"kernel":"0.26.0",
178
+ "subject":{"kind":"instance","instance":"release-manager-x","home":"/w/agents/release-manager/instances/release-manager-x","soul":"release-manager"},
179
+ "workspace":{"key":"github.com/northwind/agents","name":null,"deployment":"/w","commit":"461b9c24…","standalone":false},
180
+ "souls":[{"soulsApi":2,"name":"release-manager","repoKey":"github.com/northwind/agents","commit":"461b9c24…","team":"engineering",
181
+ "kind":null,"path":null,"description":"Cuts, verifies and announces platform releases.","work":"worktree","harness":null,"model":null,
182
+ "declarations":{"requires":null,"defaults":null,"knowledge":{"owns":"release-manager","reads":["platform-engineer"]},"teams":null,"resources":null,"children":null,
183
+ "capabilities":{"nw-release-tooling":{"from":"here"},"nw-deploy":{"from":"package"}}},
184
+ "declarationProblems":[],
185
+ "instructions":{"file":"/w/agents/release-manager/souls/461b9c24929c/AGENTS.md","text":"# release-manager\n…","truncated":false}}],
186
+ "layers":{"knowledge":{"id":"oats.okf","from":"workspace"},"messaging":{"id":null,"from":null},"tasks":{"id":null,"from":null}},
187
+ "capabilities":[
188
+ {"id":"nw-house-style","version":"0.0.0-workspace","layer":null,"command":null,
189
+ "from":{"kind":"member","repoKey":"github.com/northwind/agents","commit":"461b9c24…"},
190
+ "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":[]},
191
+ {"id":"oats.okf","version":"2.1.3","layer":"knowledge","command":"okf",
192
+ "from":{"kind":"package","package":"oats.okf","version":"2.1.3","commit":"71f53649…","integrity":"sha256-5019…","repoKey":"github.com/awebai/oats-okf"},
193
+ "dir":"/w/agents/release-manager/instances/release-manager-x/.oats/modules/oats.okf",
194
+ "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":[],
195
+ "operations":[{"name":"inspect","kind":"view","command":"inspect","context":"home","description":"…","args":[],"argv":["okf","inspect"],"available":true,"reason":null}]}],
196
+ "knowledge":{"provider":"oats.okf","version":"2.1.3","operations":[{"name":"inspect","kind":"view","available":true,"reason":null}]},
197
+ "instance":{"home":"/w/agents/release-manager/instances/release-manager-x","instance":"release-manager-x","agent":"release-manager",
198
+ "harness":"pi","model":null,"yolo":null,"launched":false,"createdAt":"<iso>","resolution":"7217670b…",
199
+ "soulDir":"/w/agents/release-manager/souls/461b9c24929c",
200
+ "instructions":{"file":"/w/agents/release-manager/instances/release-manager-x/AGENTS.md","text":"…","truncated":false,
201
+ "sources":[{"source":"kernel:instance-boundary","file":"…"},{"source":"capability:oats.okf","file":"…/.oats/modules/oats.okf/injects/okf.md"}]}},
202
+ "identity":null,"problems":[]}
203
+ ```
204
+
205
+ - `subject` is `{kind:"instance", instance, home, soul}` for `--home` (the
206
+ `home` as you passed it) or `{kind:"soul", soul, repoKey, commit, team}` for
207
+ `--soul`.
208
+ - `workspace.deployment` is canonical (realpath). `workspace.name` is
209
+ observed, so it is `null` on `inspect --home`: that command never contacts
210
+ the remotes, and `instance.json` records the workspace `key`, not its name.
211
+ Identify the workspace by `key`.
212
+ - `souls` holds exactly the subject's soul. For a home, it is read from the
213
+ recorded `soulDir` (the per-commit copy the instance incarnates), with
214
+ `path: null`. For a soul, it is the member's current definition, with
215
+ `path` inside the member repository. `kind` is `member` or `external`.
216
+ It is observed from discovery, so it is `null` on `inspect --home`
217
+ (readiness `--home` observes it).
218
+ `declarations` gains `capabilities` (the soul's own `capabilities:`).
219
+ - `layers.<layer>` is `{ id, from }`: the capability filling the slot
220
+ (`null` when empty) and where it came from (feature `layers-from`):
221
+ `"soul"` (the soul's own `capabilities:`), `"workspace"`
222
+ (`defaults.<slot>` or `defaults.capabilities`) or `"team:<label>"`
223
+ (`defaults.byTeam.<label>.capabilities`). `from` is `null` for an empty
224
+ slot. A soul answers from its resolution now. A home answers what its spawn
225
+ recorded, even after the workspace changes. A home spawned before
226
+ `layers-from` recorded nothing, so its `from` is `null`.
227
+ - `capabilities[]` lists the subject's resolved modules, sorted by id:
228
+ - `dir` is the home's module copy, or `null` for a soul (nothing is
229
+ materialized to answer inspect).
230
+ - `settings` is the merged provider payload.
231
+ - `compatibility` is `{ ok, range, kernel }`: the manifest's
232
+ `compatibility.oats` (`null` when none) against the running kernel. A
233
+ soul's resolution refuses an incompatible module (`E_CAPABILITY_INCOMPATIBLE`),
234
+ so `ok: false` appears only for a home, together with a
235
+ `capability-incompatible` entry in `problems`.
236
+ - `declares` lists the setting keys the manifest declares (`settings.<key>`),
237
+ sorted; names only, never descriptions or defaults; `[]` when it declares
238
+ none. Gate on feature `settings-declared` (e.g. offer a Teams choice only
239
+ when the messaging module declares `join`).
240
+ - `missingRequires` lists the manifest `requires` commands absent from PATH.
241
+ - `operations[].available` is `false` with a `reason` when it cannot run
242
+ here: a `context: "home"` operation for a soul subject says `needs a
243
+ running home (--home)`.
244
+ - `instance` is `null` for a soul. For a home, `instructions.sources` names
245
+ each composed inject in order.
246
+ - A soul whose resolution is refused (for example, a package the lock does
247
+ not provide) is an error for inspect (`E_PACKAGE_MISSING`,
248
+ `E_PACKAGE_INTEGRITY`, `E_CAPABILITY_MISSING`, `E_LOCK_SCHEMA`). Readiness
249
+ reports the same condition as a failing item.
250
+
251
+ ### `oats readiness (--home <abs> | --soul <name> [--dir <d>]) [--policy] --json` → `readinessApi: 2`
252
+
253
+ ```json
254
+ {"readinessApi":2,
255
+ "subject":{"kind":"soul","soul":"release-manager","repoKey":"github.com/northwind/agents","commit":"461b9c24…","team":"engineering"},
256
+ "selector":{"kind":"soul","soul":"release-manager","agentsRoot":null,"dir":"/w"},"at":"<iso>",
257
+ "checks":{
258
+ "installed":{"status":"pass","items":[
259
+ {"subject":"oats.okf","status":"pass","required":true,"reason":null,"producer":"workspace resolution",
260
+ "evidence":{"from":{"kind":"package","package":"oats.okf","version":"2.1.3","commit":"71f53649…","integrity":"sha256-5019…"}},"remedy":null,"capability":{"id":"oats.okf"}}]},
261
+ "configured":{"status":"not-applicable","items":[]},
262
+ "member":{"status":"pass","items":[
263
+ {"subject":"member github.com/northwind/agents","status":"pass","required":true,"reason":null,"producer":"workspace discovery",
264
+ "evidence":{"repoKey":"github.com/northwind/agents","workspace":"github.com/northwind/agents","commit":"461b9c24…"},"remedy":null}]},
265
+ "providers":{"status":"fail","items":[
266
+ {"subject":"oats.okf","status":"fail","required":true,"reason":"setting state-dir is required (absolute host path)","producer":"provider binding check",
267
+ "evidence":null,"remedy":null,"capability":{"id":"oats.okf"},
268
+ "result":{"status":"needs-configuration","problems":[{"code":"needs-configuration","message":"setting state-dir is required (absolute host path)"}],"warnings":[]}}]}},
269
+ "summary":{"ready":false,"required":3,"pass":2,"fail":1,"unknown":0,
270
+ "byCapability":[{"capability":{"id":"oats.okf"},"checks":{"installed":"pass","configured":"not-applicable","member":"not-applicable","providers":"fail"},"ownReady":false,"ready":false}],
271
+ "subjectBlockers":[]},
272
+ "notes":["…"]}
273
+ ```
274
+
275
+ For `--home`, `subject` is `{kind:"instance", instance, home, soul}`, and
276
+ `selector` is `{kind:"home", home, soul, agentsRoot}`. The selector echoes
277
+ your arguments byte-exact, as before. It is now a top-level field, not
278
+ `subject.selector`.
279
+
280
+ The four checks are `installed | configured | member | providers`, each
281
+ `{status, items}` with the item fields as before (`subject, status, required,
282
+ reason, producer, evidence, remedy`, plus `capability {id}` on
283
+ per-capability items). Item and check statuses are `pass | fail | unknown |
284
+ not-applicable`. **`summary.ready`** means every required item passes or is
285
+ not-applicable, and at least one required item exists. `byCapability` and
286
+ `subjectBlockers` keep their 0.24.9 meaning over the four new checks.
287
+
288
+ - **`installed`**:
289
+ - For `--home` (producer `instance modules`): each recorded module, `pass`
290
+ when its copy under `<home>/.oats/modules/<id>/` holds its `oats.json`.
291
+ A missing copy fails, with remedy "spawn a new instance".
292
+ - For `--soul` (producer `workspace resolution`): each resolved module, with
293
+ its `from` as evidence.
294
+ - A resolution refusal is one failing item carrying `code` (`E_PACKAGE_MISSING`,
295
+ `E_PACKAGE_INTEGRITY`, `E_CAPABILITY_MISSING`, `E_LOCK_SCHEMA` or
296
+ `E_REQUIREMENT_INACTIVE`), the kernel's message as `reason`, its details as
297
+ `evidence`, and `remedy: "oats sync (…)"`. That covers a soul whose
298
+ packages are not locked, or whose lock no longer matches. The item's
299
+ `subject` is the soul; it lands in `summary.subjectBlockers`.
300
+ - **`configured`** (producer `capability manifest`): each module's manifest
301
+ `requires` command (`evidence.command`), `pass` on PATH and `fail`
302
+ otherwise, with the manifest's `install` hint as the remedy. It is `not-applicable` when nothing declares a
303
+ requirement. Settings problems are the provider's to say, in `providers`.
304
+ - **`member`** (producer `workspace discovery`): the soul's member
305
+ repository is confirmed in the workspace. It is the host's `members:` with
306
+ the member's `oats-membership.yaml` backlink, as `oats workspace status`
307
+ reports it. The kernel never reads `oats.yaml` here.
308
+ - `fail` when the backlink is not confirmed; the remedy names
309
+ `oats-membership.yaml`.
310
+ - `unknown` when discovery could not be read.
311
+ - `not-applicable` (`required: false`) for an external soul (it has no
312
+ member backlink) and on a standalone view (decision 10, an allowed mode:
313
+ membership is declared there, never confirmed). The reason starts with
314
+ `standalone view (explicit | unreadable-host)`, and
315
+ `evidence.standaloneReason` carries the reason code. A standalone soul can
316
+ therefore read Ready.
317
+ - Never login, never team registration.
318
+ - **`providers`** (producer `provider binding check`): for each module whose
319
+ manifest declares `binding`, the kernel runs the provider's own check
320
+ (`binding.check`). It relays **the provider's answer verbatim** as
321
+ `item.result: {status, problems: [{code, message}], warnings: [{code,
322
+ message}]}`. The status maps to the item:
323
+
324
+ | `result.status` | item `status` |
325
+ |---|---|
326
+ | `ready` | `pass` |
327
+ | `needs-configuration` | `fail` |
328
+ | `authorization-required` | `fail` |
329
+ | `unavailable` | `unknown` |
330
+
331
+ The reason is the first problem's message (`null` on pass).
332
+ `authorization-required` and `unavailable` were added in 0.26.0 (additive):
333
+ treat an unrecognized status as `unknown` and show `result` as sent.
334
+ - `warnings` is always present (`[]` when the provider sends none). It
335
+ **never changes the status** and is not counted in `summary`. A ready
336
+ binding can still say, for example, that end-to-end encryption is off:
337
+ show it next to the pass. A `warnings` that is not an array of `{code,
338
+ message}` strings makes the whole answer `unknown`, the same as a
339
+ malformed `problems`.
340
+ - A provider that cannot answer is `unknown`, with `item.problems` carrying
341
+ its error `{code, message}` and `result: null`. That covers:
342
+ - a refusal (`ok:false`, whose code is relayed);
343
+ - an invalid answer (`provider-unavailable`, see the wire below);
344
+ - a timeout;
345
+ - a module tree that cannot be made available.
346
+ - **One time budget per readiness read**: 60 s for all provider checks
347
+ together, and at most 30 s for each. Checks the budget does not reach are
348
+ not run; they are `unknown` with code `time-budget-exhausted`.
349
+ - For `--home` the check runs from the home's module copy, as the home's
350
+ hooks do. For `--soul` it runs from the module in the deployment's module
351
+ store (the tree `oats <ns> …` dispatch uses). A store tree is used only
352
+ while its content digest matches the digest verified when it was fetched
353
+ at the locked commit. A drifted tree is fetched again, and a fetch that
354
+ does not verify is `E_PACKAGE_INTEGRITY` (the item is `unknown` with that
355
+ code).
356
+ - A module without `binding` has no item; the check is `not-applicable`
357
+ when there are none.
358
+ - This check reads the provider; it does not bind. A spawn's fail-closed
359
+ hooks are unchanged.
360
+ - **Removed:** `trusted` and its `signature` block (declaring a package in
361
+ `packages:` is the trust decision). `--verify-signatures` answers
362
+ `E_BAD_ARGS`. `enrolled` is now `member`.
363
+
364
+ `--policy` is **kept**: it means the same without the chain. With `--home` it
365
+ is the instance's recorded, enforced policy (`instance.json` `policy`, plus
366
+ the recorded work mode). With `--soul` it is the soul's declaration
367
+ (`children.spawn`, `work`), `enforced: false`. The shape is unchanged:
368
+
369
+ ```json
370
+ "policy":{"childSpawns":{"allowed":true,"enforced":true,"origin":{"kind":"default","detail":"no declaration: children allowed"}},
371
+ "worktrees":{"allowed":false,"mode":"directory","enforced":true,"origin":{"kind":"work-mode","detail":"work: directory"}}}
372
+ ```
373
+
374
+ **The provider check wire (the request `binding.check` receives).** The
375
+ request is one JSON line on stdin, and the provider answers one envelope line
376
+ on stdout:
377
+
378
+ ```json
379
+ {"schemaVersion":1,"phase":"check","slot":"knowledge","capability":"oats.okf","settings":{"…":"the merged payload"},
380
+ "input":{"context":{"kind":"workspace","workspace":"<workspace key>","deployment":"/w","soul":"release-manager","team":"engineering",
381
+ "instance":"release-manager-x","home":"/w/agents/…/release-manager-x"},
382
+ "action":{"kind":"readiness"}}}
383
+ ```
384
+
385
+ ```json
386
+ {"schemaVersion":1,"phase":"check","slot":"knowledge","capability":"oats.okf","ok":true,
387
+ "result":{"status":"ready","problems":[],"warnings":[]}}
83
388
  ```
84
389
 
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.
390
+ The environment is the provider's module environment:
391
+ - `OATS_CAPABILITY`, `OATS_SETTINGS`, `OATS_CLI_BIN` and `OATS_WORKSPACE`;
392
+ - the team variables (`OATS_TEAM_*`, `OATS_WORKSPACE_NAME`/`_KEY`);
393
+ - `OATS_AGENT` (the soul), and `OATS_SOUL` when the soul directory is known;
394
+ - for a home, `OATS_INSTANCE` and `OATS_INSTANCE_HOME`.
395
+
396
+ For a home, `OATS_WORKSPACE_NAME` is `""` until spawn records the workspace
397
+ name (planned).
398
+
399
+ Ambient `OATS_*`/`PI_*` is removed. For a soul, `instance` and `home` are
400
+ `null`. The answer is decoded by the binding wire's response rules:
401
+ - the process exits 0;
402
+ - stdout is exactly one JSON document within the wire limits;
403
+ - the envelope has exactly `schemaVersion`, `phase`, `slot`, `capability`,
404
+ `ok` and `result` (or `error`), echoing the request's first four;
405
+ - `result` has `status`, `problems` and optionally `warnings`, and nothing else;
406
+ - `ready` carries no problems;
407
+ - problems and warnings are `{code, message}` strings. Their codes are the
408
+ provider's own and are not checked against `binding.reasons`.
409
+
410
+ Anything else is `unknown` (`provider-unavailable`). The check executable must
411
+ resolve (realpath) inside its module directory and be a regular file; otherwise
412
+ the item is `unknown` (`resource-not-found`). The request carries no
413
+ `binding`. A provider whose check
414
+ still requires one answers `invalid-binding`, and readiness reports it as
415
+ `unknown`.
416
+
417
+ ### `oats operation run <layer>:<name> (--home <abs> | --soul <name> [--dir <d>]) [--arg k=v …] --json` → `operationsApi: 2`
418
+
419
+ ```json
420
+ {"operationsApi":2,"operation":"knowledge:inspect","capability":"oats.okf","version":"2.1.3","argv":["okf","inspect"],
421
+ "cwd":"/w/agents/release-manager/instances/release-manager-x",
422
+ "target":{"home":"/w/agents/release-manager/instances/release-manager-x","instance":"release-manager-x"},
423
+ "result":{"documents":[{"label":"Working state (STATE.md)","kind":"markdown","text":"…"}]}}
424
+ ```
425
+
426
+ The provider is the module that fills `<layer>`:
427
+ - for `--home`, the home's module copy, with its recorded settings;
428
+ - for `--soul`, the resolved module, materialized into the deployment's module
429
+ store if needed.
430
+
431
+ The rest of the contract is unchanged ([operations contract](design/operations-contract.md)):
432
+ - errors: `E_OPERATION_UNKNOWN`, `E_OPERATION_UNAVAILABLE` (also a
433
+ `context: "home"` operation without `--home`), `E_CAPABILITY_REQUIRES`;
434
+ - the receipt rules and the `E_OPERATION_TIMEOUT` / `E_OPERATION_RESULT`
435
+ unconfirmed outcomes.
436
+
437
+ There is no `E_CAPABILITY_BLOCKED` (no trust gate). `cwd` is the home for a
438
+ `context: "home"` operation, and the deployment otherwise.
439
+
440
+ **Remote.** `--server` routes as before. The destination must advertise
441
+ `operations` with `operationsApi` 1 or 2; a 0.26 CLI routes to either.
442
+ Payload shapes are the destination kernel's.
443
+
444
+ ## Souls and sources (`oats inspect --json`, `soulsApi: 1`) — removed in 0.26.0
445
+
446
+ The classic scope document (`souls[].provenance`, `souls[].readiness`, the
447
+ scope's portable `sources`) was removed with the classic config chain.
448
+ `oats inspect` answers only [`soulsApi: 2`](#oats-inspect---home---soul---dir---json-operationsapi-2)
449
+ rows; the soul's declarations are in `oats souls --json`.
92
450
 
93
451
  ## Instance Git state (`oats instance git|diff`, `instanceGitApi: 1`, OATS 0.24.7+)
94
452
 
@@ -170,7 +528,7 @@ home's removal, so a retired instance's `retired` event is still readable).
170
528
 
171
529
  ```json
172
530
  {"eventsApi":1,"instance":"dev-1","home":"/abs/home","count":7,"returned":7,"truncated":false,
173
- "events":[{"eventsApi":1,"at":"<iso>","instance":"dev-1","home":"/abs/home","producer":"kernel","kind":"spawned","data":{"agent":"dev","work":"worktree","branch":"agents/dev-1","runtime":"claude","model":null,"parentInstance":null,"relation":null,"launched":true}},
531
+ "events":[{"eventsApi":1,"at":"<iso>","instance":"dev-1","home":"/abs/home","producer":"kernel","kind":"spawned","data":{"agent":"dev","work":"worktree","branch":"agents/dev-1","harness":"claude","model":null,"parentInstance":null,"relation":null,"launched":true}},
174
532
  {"…":"launched | restarted | stopped | stop-refused | retire-planned | worktree-retained | worktree-removed | branch-deleted | retired | child-spawn-refused"}],
175
533
  "lastEvent":{"kind":"stopped","at":"<iso>","producer":"kernel"},
176
534
  "waitingOnYou":null,
@@ -258,8 +616,8 @@ deduplicated per run. Where the run launched or targeted an instance, a
258
616
  session to open (the existing `oats session` surface); the kernel does not
259
617
  copy transcripts. `nextRun`/`lastRun`/`executionStatus` are unchanged. The
260
618
  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).
619
+ transcript pointer as the handoff (definition fields are untouched by this
620
+ addition). A stored captured definition (removed in 0.26) lists as `invalid`.
263
621
 
264
622
  ### History API 3 (`scheduleHistoryApi: 3`, feature `schedule-read-2`, OATS 0.24.13+) — K8b
265
623
 
@@ -327,20 +685,21 @@ unchecked, echoed a stored `definition.id` without checking it, and named a
327
685
  The Spawn modal's fields are backed by the kernel's own decision, taken **before
328
686
  any side effect**: `oats spawn <agent> [same flags as a real spawn] --preview --json`
329
687
  runs every preflight a spawn runs (placement, composition, resources,
330
- executable, runtime packages, child-spawn policy) and returns what the spawn
688
+ executable, harness packages, child-spawn policy) and returns what the spawn
331
689
  *would* do — then returns without creating a home, branch or worktree.
332
690
 
333
691
  ```json
334
692
  {"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",
693
+ "repo":"/abs/repo","work":"worktree","harness":"claude","model":"opus","modelSource":"explicit","launchConfig":null,"yolo":false,"backend":"tmux",
336
694
  "branch":"agents/dev-fix-login","base":{"ref":"HEAD","oid":"<oid>"},"worktree":"/abs/agents/dev/instances/dev-fix-login/work",
337
695
  "relation":null,"parentInstance":null,"policy":{"childSpawns":{"allowed":true,"origin":{"kind":"default","detail":"…"}}},
338
696
  "executable":"/abs/bin/claude","capabilities":["oats.core"],"skills":["oats-operate","oats-souls"],"task":"…"}
339
697
  ```
340
698
 
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.
699
+ - **Name / work area**: `instance` is the name: by default the derived shape
700
+ `<agent>-<purpose>` (de-duplicated with `-2`, `-3`…), or exactly the
701
+ `--name <slug>` the caller gave (see *Instance names* below); `home` and
702
+ `worktree` are the canonical paths. The renderer never derives paths.
344
703
  - **Branch / base** (worktree mode): `branch` defaults to `agents/<instance>`
345
704
  (`--branch <name>` overrides; validated); `base` is `--base <ref>` resolved
346
705
  to its commit oid (default `HEAD`). `E_BRANCH_EXISTS` and `E_BASE_UNKNOWN`
@@ -348,7 +707,7 @@ executable, runtime packages, child-spawn policy) and returns what the spawn
348
707
  the worktree **from that exact oid**.
349
708
  - **Model**: `model`/`modelSource` are the resolved selection. Omitting
350
709
  `--model` **inherits** the launch configuration's or soul's preference;
351
- `--model @native-default` is the explicit "use the runtime's own default"
710
+ `--model @native-default` is the explicit "use the harness's own default"
352
711
  (`modelSource: "native default (explicit)"`). These are different requests
353
712
  and the UI must not relabel one as the other.
354
713
  - **Policy**: `policy.childSpawns` is what this instance will record (soul
@@ -376,15 +735,13 @@ the pre-fix marker and is never accepted for dispatch.
376
735
  import; `--instructions-file`/`--def-file` refused with `E_BAD_ARGS`). Test:
377
736
  the deployment tree is byte-identical after a success, a refusal and an
378
737
  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.
738
+ **Workspace deployments (0.26.0+)**: this holds for the FIRST preview of a
739
+ soul or commit too. A preview reads the soul from the deployment's per-commit
740
+ cache (`agents/<soul>/souls/<commit>/`) when a spawn already filled it, else
741
+ fetches it to a temporary copy outside the deployment and removes it
742
+ (`soulFetched: true` in the result). Only a spawn fills the cache or moves the
743
+ `agents/<soul>/soul` pointer. (0.25.x previews populated the cache: that stated
744
+ exception is gone.)
388
745
  - **Exact root**: `spawn <soul> --agents-root <abs>` binds the soul to that root
389
746
  (as inspect/readiness take it) — no team-soul / capability-agent / importable-
390
747
  def fallback; mismatch → `E_SOUL_UNKNOWN`. The preview echoes
@@ -395,7 +752,19 @@ the pre-fix marker and is never accepted for dispatch.
395
752
  per-module payload each provider will receive (`{ "<cap>": {…} }`, exactly
396
753
  `settings.<cap>` of the preview) — so a confirmed apply binds every
397
754
  provider fact (an identity choice, a delivery mode) **by value**; a Desktop
398
- that changes a provider field re-previews. `instances[].identity` (status)
755
+ that changes a provider field re-previews. From 0.26.0 the merged payload
756
+ includes the manifest's declared setting defaults (`settings.<key>.default`,
757
+ the lowest layer), and the preview's **`settingsOrigins.<cap>`** maps each
758
+ leaf of `settings.<cap>` (a JSON pointer, e.g. `/identity/mode`) to
759
+ `{ kind, at }`: `kind` is `manifest-default` | `workspace` | `soul` |
760
+ `host` | `spawn` — the last layer that set it. (`workspace-team` no longer
761
+ appears since teams amendment K: a label's `byTeam` entry is not merged into
762
+ the settings; it is in `teams[].payload`.) —
763
+ and `at` names where (`oats.json#/settings/identity/default`,
764
+ `soul.yaml#/messaging`, `oats-local.yaml#/settings/<cap>`,
765
+ `--provider <cap>`, …). A Desktop labels `manifest-default` values
766
+ "Default" from this, instead of hardcoding them (feature
767
+ **`settings-origins`**). `instances[].identity` (status)
399
768
  and `selected.identity` (inspect) carry the served principal a messaging
400
769
  provider reported: `{ mode: "local"|"global", alias, team, address|null,
401
770
  resident|null, grant?: { id, expiresAt, scopes }, provider }`; absent when
@@ -410,11 +779,11 @@ the pre-fix marker and is never accepted for dispatch.
410
779
  - **Bounded preflight**: every native probe a preview runs (`pi --list-models`,
411
780
  `pi list`, `claude plugin list`) shares ONE budget (20 s default), runs in its
412
781
  own process group and is group-killed on timeout; `preflight {status:
413
- complete|timeout, budgetMs, elapsedMs}` says which. A hanging runtime cannot
782
+ complete|timeout, budgetMs, elapsedMs}` says which. A hanging harness CLI cannot
414
783
  hang a preview.
415
784
  - **Confirmed apply contract** (0.24.10+, feature `spawn-apply-2`,
416
785
  `spawnApplyApi: 1`) — what a GUI may promise at "Confirm spawn":
417
- - `decision` gains **`effective {repo, work, runtime, model, launchConfig,
786
+ - `decision` gains **`effective {repo, work, harness, model, launchConfig,
418
787
  yolo, backend, childSpawns, relation{kind, anchor{instance, agentsRoot}}}`**
419
788
  and `revision` hashes placement + effective. An inherited default that would
420
789
  change what launches (the soul's model edited between preview and apply,
@@ -429,7 +798,12 @@ the pre-fix marker and is never accepted for dispatch.
429
798
  immediately after the decision check; a concurrent spawn that lost refuses
430
799
  **`E_PLACEMENT_TAKEN`** having touched nothing. Two concurrent applies of
431
800
  one decision yield exactly one home. There is no wider lock; this
432
- reservation is the guarantee.
801
+ reservation is the guarantee. Instance names are deployment-wide
802
+ (0.26.0), so right after its reservation a spawn re-checks the whole agents
803
+ root: when another soul's concurrent spawn reserved the same name, it
804
+ removes its own empty reservation and refuses (`E_INSTANCE_NAME_TAKEN` for a
805
+ `--name`, `E_PLACEMENT_TAKEN` for a derived name). At most one wins, and
806
+ possibly neither.
433
807
  - Gate confirmation AND the exec owner on `spawn-preview-2` +
434
808
  `spawn-apply-2` + `spawn-idempotency`; a legacy local request on such a CLI
435
809
  is refused by the Desktop (`E_PLAN_REQUIRED`), not routed around the fence.
@@ -468,75 +842,15 @@ the pre-fix marker and is never accepted for dispatch.
468
842
  (provider contract), auto-PR (P1/ADE write approval), branch enumeration
469
843
  (producer seam).
470
844
 
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
- ```
845
+ ## Readiness quartet (`readinessApi: 1`) — removed in 0.26.0
509
846
 
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:
847
+ The quartet (`installed | trusted | configured | enrolled`), its signature
848
+ verification (`--verify-signatures`, feature `readiness-verify`) and the
849
+ scope subject were removed with the classic config chain. `oats readiness`
850
+ answers only [`readinessApi: 2`](#oats-readiness---home---soul---dir---policy---json-readinessapi-2);
851
+ `--verify-signatures` is `E_BAD_ARGS`.
535
852
 
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
- ```
853
+ ### Enforced child-spawn policy (`--policy`)
540
854
 
541
855
  `childSpawns` is **enforced by the spawn route**: `soul.yaml` may declare
542
856
  `children: {spawn: false}`; `oats spawn --allow-child-spawns | --no-child-spawns`
@@ -550,58 +864,6 @@ instance's recorded (enforced) one; with only `--soul` it is the declaration
550
864
  (`enforced: false`). It is a lifecycle-authority claim, not an OS sandbox —
551
865
  the UI says so.
552
866
 
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
867
  ## Lifecycle plans — Stop and Remove (`lifecycleApi: 1`, OATS 0.24.8+)
606
868
 
607
869
  The Desktop's Stop and Remove confirmations render **plans**: a read-only
@@ -693,6 +955,12 @@ location. The receipt says so:
693
955
  nothing is lost; retry or pass `--discard-worktree`.
694
956
  - Non-worktree modes report `retention: null`. Quarantine/rollback paths keep
695
957
  their removal semantics.
958
+ - A recovery (`workRecovery`, `workRecoveries[]`) is `{path, classes, bytes,
959
+ outputs?, repoCopy?}` (0.26.0: `bytes`, `outputs`): `bytes` is the recovery's
960
+ own size; `outputs: {paths: [{path, bytes}], bytes}` names what it copied
961
+ beyond tracked state — a worktree's untracked and ignored paths, or a
962
+ directory's work entries — grouped by top-level entry, largest first. Absent
963
+ when only home bytes were copied.
696
964
  - The Remove dialog's "also delete worktree / branch" checkboxes map to these
697
965
  two flags; the kernel never touches a PR.
698
966
  - **Guarded apply** (what a GUI sends): `oats retire <i> --plan-revision <rev>
@@ -730,7 +998,8 @@ location. The receipt says so:
730
998
  `retire-retention`, `readiness`, `spawn-preview`, `instance-events`,
731
999
  `schedule-history`, and carries the API integers (`instanceGitApi`, `soulsApi`,
732
1000
  `lifecycleApi`, `readinessApi`, `spawnPreviewApi`, `eventsApi`,
733
- `scheduleHistoryApi`). **Gate on these, never on a version string and never by
1001
+ `scheduleHistoryApi`, `operationsApi`). In 0.26.0, `soulsApi`, `readinessApi`
1002
+ 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
1003
  optimistic invocation**: an older CLI ignores an unknown `--plan` on `retire`
735
1004
  and *retires*. Absent feature → the view is unavailable. (`catalog` was the
736
1005
  0.24 `oats catalog` verb's flag; the verb is removed under the workspace model
@@ -772,13 +1041,12 @@ machine in the directory the operator chooses (any existing folder). It writes
772
1041
  `<dir>/oats-local.yaml` (`{ schemaVersion: 2, workspace: <ref> }`), creates
773
1042
  `<dir>/agents/` (the instance homes), then runs exactly the `oats sync` body
774
1043
  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
1044
+ membership, resolve `packages:`, write `oats-lock.json`. It installs nothing, creates no soul, spawns nothing
777
1045
  and writes no `oats-config.yaml`; the member clones and the setup-expert spawn
778
1046
  are printed as next steps. `<dir>` defaults to cwd; `--dir <d>` is the same
779
1047
  argument (give it once). `--workspace` is required and must be a ref
780
1048
  `lib/remote.mjs` parses (`E_REPO_REF`) — checked **before** anything is
781
- written. Captured selectors are refused (`E_BAD_ARGS`).
1049
+ written. Captured selectors are refused (`E_UNSUPPORTED_MODE`: the captured/portable path was removed in 0.26).
782
1050
 
783
1051
  ```json
784
1052
  {"onboardApi":2,
@@ -793,13 +1061,9 @@ written. Captured selectors are refused (`E_BAD_ARGS`).
793
1061
  ```
794
1062
 
795
1063
  - `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.
1064
+ changes, `problems`); `lock` is the lock it wrote. Exit `0` on success —
1065
+ there is no approval-pending outcome (0.26.0, feature
1066
+ `packages-no-approval`; earlier kernels exited `2` with `approvalNeeded`).
803
1067
  - `hosting` states decision 26 (the kernel cannot see forge visibility, so it
804
1068
  reports `hostIsMember` and the rule rather than judging).
805
1069
  - `next.clone[]` is one row per **confirmed** member (`url` = what the
@@ -822,12 +1086,18 @@ written. Captured selectors are refused (`E_BAD_ARGS`).
822
1086
 
823
1087
  ### `oats sync [--dir <d>] --json` → `syncApi: 1`
824
1088
 
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.
1089
+ Discovers, confirms membership, resolves `packages:` to commits + integrity,
1090
+ writes `oats-lock.json` (lockfileVersion 3), reports; exit `0` on success.
1091
+ **No package approval** (0.26.0, human decision 2026-09-24; feature
1092
+ `packages-no-approval`): declaring a package in `packages:` is the trust
1093
+ decision. The report has no `approvalNeeded`, package rows no `approved`,
1094
+ `changes[]` rows no `approvalNeeded`; there is no prompt and no exit `2`, and
1095
+ `--approve` is `E_BAD_ARGS`. A lock written by an earlier kernel keeps working
1096
+ (its `approved` records are ignored and dropped on the next write). The fields
1097
+ went away without an API-number bump — `syncApi`, `workspaceStatusApi` and
1098
+ `capabilitiesApi` stay `1`; the removal is signalled by the feature string
1099
+ alone — so a consumer reading `approvalNeeded`, `approval` or `approved` must
1100
+ gate that on the absence of `packages-no-approval`.
831
1101
 
832
1102
  ```json
833
1103
  {"syncApi":1,
@@ -839,12 +1109,9 @@ the TTY fallback, not the contract. Exit `0` otherwise.
839
1109
  "souls":["tools-expert"],"capabilities":["acme-tools-dev"],"publishes":{"package":"acme.tools","version":"0.4.0"}},
840
1110
  {"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
1111
  "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"]}],
1112
+ "packages":[{"id":"oats.okf","version":"2.1.3","source":"catalog:oats.okf","commit":"<oid>","integrity":"sha256-…","capabilities":["oats.okf"]},
1113
+ {"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"]}],
1114
+ "changes":[{"id":"acme.tools","from":null,"to":"0.4.0","commit":"<oid>"}],
848
1115
  "problems":[]}
849
1116
  ```
850
1117
 
@@ -854,17 +1121,14 @@ the TTY fallback, not the contract. Exit `0` otherwise.
854
1121
  capabilities are **not** in `capabilities[]`; the non-collapse rule).
855
1122
  - `changes[]`: `from` = previously locked version or `null`; `to` = `null` when
856
1123
  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
1124
  - `problems[]`: `{ code, path, message, repoKey? }` — per-item discovery
861
1125
  problems (`E_WORKSPACE_SCHEMA`, `E_TEAM_UNKNOWN`, `E_REMOTE_*`, …). Never an
862
1126
  abort: an unreadable member directory is a problem of that member.
863
1127
  - Errors: `E_LOCAL_MISSING`, `E_WORKSPACE_SCHEMA { path, problems[] }`,
864
1128
  `E_REMOTE_UNREADABLE`, `E_LOCK_SCHEMA`, `E_PACKAGE_MISSING` (catalog has no
865
1129
  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`.
1130
+ `E_PACKAGE_MANIFEST`, `E_REPO_REF`, `E_BAD_ARGS` (`--approve`: package
1131
+ approval was removed).
868
1132
 
869
1133
  ### `oats package add <id> <version|git:<repo>@<ref>> | remove <id> [--dir] --json`
870
1134
 
@@ -896,18 +1160,28 @@ found to check the declaration even though it does not edit it. `E_USAGE`,
896
1160
  "declaredPackages":["acme.tools","oats.okf"],
897
1161
  "unsynced":[],
898
1162
  "stale":[],
899
- "approval":{"approved":["oats.okf"],"needed":["acme.tools"]},
900
1163
  "external":[{"source":"git:github.com/oss-collective/experts@<oid>","soul":"security-reviewer","team":"unassigned"}],
901
- "problems":[]}
1164
+ "problems":[],
1165
+ "warnings":[]}
902
1166
  ```
903
1167
 
1168
+ `warnings[]` (feature `teams`, also in the `sync` report): `{ code, label,
1169
+ souls, paths, message }` — one `unmapped-team-label` per label that is in
1170
+ `teams:` but not in `messaging.byTeam`, naming its souls (sorted) and each
1171
+ soul's `<repoKey>:<path>#/team`; sorted by label. Never a problem.
1172
+
904
1173
  `unsynced` = declared in `packages:` but not in the lock (run `sync`);
905
1174
  `stale` = locked but no longer declared. Read-only: does not write the lock.
1175
+ (0.26.0: the `approval` object is gone with package approval.)
906
1176
 
907
1177
  ### `oats capabilities [--dir] --json` → `capabilitiesApi: 1` · `oats souls [--dir] --json` → `soulsApi: 1`
908
1178
 
909
- Every **non-private** item of every confirmed member, external souls, and
910
- locked package capabilities, sorted by name then origin. `origin` is the
1179
+ Every item of every confirmed member, external souls, and locked package
1180
+ capabilities, sorted by name then origin. Souls have no private mode (their
1181
+ `private` is always `false`); a repo-owned capability is listed with
1182
+ `private: true` — usable only by its own repo's souls. The Desktop shows its
1183
+ "Repo owned" section when `version --json` lists the `capabilities-private`
1184
+ feature. `origin` is the
911
1185
  human string (`member <key> @ <8-char commit>` | `package <id> v<version>` |
912
1186
  `external <key> @ <commit>`); `kind` is the machine field. `team` is the label
913
1187
  or `"unassigned"`.
@@ -918,7 +1192,7 @@ or `"unassigned"`.
918
1192
  {"name":"acme-house-style","origin":"member github.com/acme/agents @ 3f2a9c1e","kind":"member","repoKey":"github.com/acme/agents","commit":"<oid>",
919
1193
  "team":"global","private":false,"path":"capabilities/acme-house-style","layer":null,"version":"0.0.0-workspace"},
920
1194
  {"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}],
1195
+ "team":"unassigned","private":false}],
922
1196
  "problems":[]}
923
1197
  ```
924
1198
 
@@ -932,8 +1206,9 @@ or `"unassigned"`.
932
1206
  ```
933
1207
 
934
1208
  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.)
1209
+ (`oats souls --json` keeps `soulsApi: 1` in 0.26.0: its shape is unchanged.
1210
+ The probe's `soulsApi` is **2** because it tracks the `oats inspect --json`
1211
+ soul rows. The two payloads are distinguished by their command.)
937
1212
 
938
1213
  ### `oats spawn <soul> … --preview --json` — additions (Preview API 2 unchanged)
939
1214
 
@@ -943,10 +1218,10 @@ between preview and apply is `E_DECISION_STALE`):
943
1218
 
944
1219
  ```json
945
1220
  {"modules":[
946
- {"name":"acme-release-tooling","from":{"kind":"member","repoKey":"github.com/acme/agents","commit":"<oid>"},"layer":null,"private":false,
1221
+ {"name":"acme-release-tooling","from":{"kind":"member","repoKey":"github.com/acme/agents","commit":"<oid>"},"layer":null,"private":false,"declares":[],
947
1222
  "changedSince":{"instance":"release-manager-v2","was":"<old oid>"}},
948
1223
  {"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}],
1224
+ "layer":"knowledge","private":false,"declares":["bindings-file","git-timeout","harvest-model","harvest-runtime","state-dir"],"changedSince":false}],
950
1225
  "team":"engineering",
951
1226
  "resolution":"6e3050c0d005879441ab017d",
952
1227
  "workspace":"github.com/acme/agents",
@@ -956,6 +1231,8 @@ between preview and apply is `E_DECISION_STALE`):
956
1231
  ```
957
1232
 
958
1233
  - `modules[].from` is exactly the `from` recorded in `instance.json` on apply.
1234
+ - `modules[].declares`: the manifest's declared setting keys, as in `inspect`
1235
+ (feature `settings-declared`).
959
1236
  - `changedSince`: `null` (no previous instance of this soul), `false`
960
1237
  (unchanged since the newest previous instance), or
961
1238
  `{ instance, was }` (`was` = the previous commit, or `null` when the previous
@@ -982,7 +1259,7 @@ between preview and apply is `E_DECISION_STALE`):
982
1259
  members or externals), `E_SOUL_AMBIGUOUS { name, repos[] }` (name it
983
1260
  `<repo>/<soul>`). Resolution errors keep their codes (`E_NOT_A_MEMBER`,
984
1261
  `E_MEMBERSHIP_UNCONFIRMED { repoKey, reason }`, `E_CAPABILITY_MISSING { hint? }`,
985
- `E_CAPABILITY_PRIVATE`, `E_PACKAGE_MISSING`, `E_PACKAGE_UNAPPROVED { id, version, commit }`,
1262
+ `E_CAPABILITY_PRIVATE`, `E_PACKAGE_MISSING`, `E_PACKAGE_INTEGRITY { why: "capabilities", listed, locked }`,
986
1263
  `E_SLOT_CONFLICT { slot, modules[], reason? }`, `E_SKILL_DUPLICATE { name, modules[] }`,
987
1264
  `E_COMPATIBILITY { capability, package, version, range, why? }`).
988
1265
 
@@ -1001,11 +1278,18 @@ Written by materialization inside the spawn transaction; read back by
1001
1278
  "oats.okf":{"from":{"kind":"package","package":"oats.okf","version":"2.1.3","commit":"<oid>","integrity":"sha256-…","repoKey":"github.com/awebai/oats-okf"},
1002
1279
  "commit":"<oid>","digest":"sha256-…","materializedAt":"<iso>"}},
1003
1280
  "providers":{"acme-release-tooling":{},"oats.okf":{"owns":"release-manager","reads":["platform-engineer"],"state-dir":"/Users/ana/.oats/okf"}},
1004
- "workspace":{"key":"github.com/acme/agents","commit":"<oid>","resolution":"<24 hex>","standalone":false,
1281
+ "workspace":{"key":"github.com/acme/agents","name":"acme","deployment":"/Users/ana/acme-workspace","commit":"<oid>","resolution":"<24 hex>","standalone":false,
1005
1282
  "soul":{"repoKey":"github.com/acme/agents","commit":"<oid>","team":"engineering"}},
1006
1283
  "capabilities":[{"id":"oats.okf","capability":"oats.okf","origin":"package:oats.okf@2.1.3","…":"one row per module"}]}
1007
1284
  ```
1008
1285
 
1286
+ `workspace.name` (the workspace file's `name`) and `workspace.deployment` (the
1287
+ directory holding the deployment's `oats-local.yaml`) are recorded from 0.26.0,
1288
+ so a home answers them without discovery: `oats inspect --home` reports the
1289
+ recorded name, and `oats operation run --home` hands it to the provider as
1290
+ `OATS_WORKSPACE_NAME`. Homes spawned
1291
+ before 0.26.0 lack both; the name is then discovered, or `null`.
1292
+
1009
1293
  `workspace.standalone` is `true` when the instance was spawned from the
1010
1294
  **standalone view** (decisions 10/25: a *member* whose workspace could not be
1011
1295
  read — `workspace.key` is then the member repo's key, and `modules` holds the
@@ -1014,9 +1298,14 @@ spawn. The same view is marked `standalone: true` in `oats sync --json` (with
1014
1298
  `workspace.name` = `standalone:<repo>`) and in the roster. `capabilities[]` is
1015
1299
  the per-module row set (`toCapabilityRows`) that `oats inspect`/`status` read.
1016
1300
 
1301
+ `soulDir` (0.26.0) is the absolute soul directory the instance incarnates — a
1302
+ workspace soul's per-commit copy `<deployment>/agents/<soul>/souls/<commit12>`, or
1303
+ the read-only soul inside a capability package — and is what every classic
1304
+ lifecycle hook and dispatched command receives as `OATS_SOUL`. Instance homes carry no `soul` link.
1305
+
1017
1306
  `digest` is the sha256 of the copied module tree (`<home>/.oats/modules/<cap>/`);
1018
- `providers.<cap>` is the merged payload (soul ⊕ `oats-local.yaml`
1019
- `settings.<cap>` ⊕ `--provider`), `{}` for a capability with none. Copies live at
1307
+ `providers.<cap>` is the merged payload (manifest defaults ⊕ soul ⊕
1308
+ `oats-local.yaml` `settings.<cap>` ⊕ `--provider`), `{}` for a capability with none. Copies live at
1020
1309
  `<home>/.oats/modules/<cap>/` and `<home>/.agents/skills/<cap>/<skill>/`.
1021
1310
 
1022
1311
  ### `oats status [--dir] --json` — module drift
@@ -1047,54 +1336,59 @@ top-level `workspace` reachability field:
1047
1336
  - Text mode prints `modules: <cap> from <member|package …> @ <7-char>` lines
1048
1337
  for non-current modules (`--verbose` for all).
1049
1338
 
1339
+ ### Eligible teams (feature `teams`, OATS 0.26.0)
1340
+
1341
+ A soul's `team` may be a list of labels; the first is the primary
1342
+ ([teams contract](design/2026-09-25-teams-contract.md)). Every label is an
1343
+ **eligible** team — which the messaging provider may join on an explicit
1344
+ request (a spawn provider setting, or its own join/leave verbs); the kernel
1345
+ joins nothing. One entry per label, in soul order:
1346
+
1347
+ ```json
1348
+ {"label":"engineering","team":"aweb:acme.eng","mapped":true,"payload":{"private":"per-human","team":"aweb:acme.eng"}}
1349
+ {"label":"reviewers","team":null,"mapped":false,"payload":{"private":"per-human"}}
1350
+ ```
1351
+
1352
+ `payload` = `workspace.messaging` ⊕ `byTeam[label]` (base alone when unmapped);
1353
+ `team` = the mapped payload's team id, else `null`. No label → `[]`.
1354
+
1355
+ Where it appears:
1356
+ - `oats spawn … --preview --json`: top-level `teams` (next to `team`, which
1357
+ stays the primary label). `settings.<messaging>` stays the primary's merged
1358
+ payload and never carries `teams`.
1359
+ - `oats inspect --soul|--home --json`: top-level `teams` and `teamsSource`.
1360
+ For `--home` the teams are **live** (the soul's labels and the workspace's
1361
+ `messaging` as the deployment resolves them now, in two repository reads;
1362
+ the home's modules are unchanged): `teamsSource: "live"`, or `"recorded"`
1363
+ with the spawn-time list when the workspace cannot be read now. Providers
1364
+ get the same marker as `OATS_TEAMS_SOURCE`.
1365
+ - `instance.json`: `teams` (the spawn-time list, kept as evidence; never
1366
+ rewritten) and `workspace.soul.labels`.
1367
+ - `oats souls --json`: each row carries `labels` (`team` stays the primary).
1368
+ - A spawn, preview or `inspect --soul` whose labels give one capability
1369
+ different `defaults.byTeam` entries answers `E_TEAM_CONFLICT { capability,
1370
+ labels: [a, b], entries, paths }`.
1371
+
1050
1372
  ### Probe
1051
1373
 
1052
1374
  ```json
1053
- {"…":"…","features":["…","workspace-v2","instance-modules","spawn-provider-payload"],"workspaceApi":2}
1375
+ {"…":"…","features":["…","workspace-v2","instance-modules","spawn-provider-payload","packages-no-approval","spawn-name","settings-origins","teams"],"workspaceApi":2}
1054
1376
  ```
1055
1377
 
1056
1378
  A feature is listed only once the binary implements it. Gate `sync`/`package`/
1057
1379
  `workspace status`/`capabilities`/`souls` on `workspace-v2`; gate reading
1058
1380
  `instance.json.modules` and preview `modules[]` on `instance-modules`; gate
1059
- `--provider` on `spawn-provider-payload`.
1060
-
1061
- ## Instruction refresh (`oats session recompose`, feature `session-recompose`, OATS 0.24.8+)
1062
-
1063
- A live instance's composed `AGENTS.md` is generated at spawn and outranks any
1064
- mail or tracked file *in the running context*. When a soul changes (a role or
1065
- budget amendment) and a respawn is not possible or wanted, an operator
1066
- refreshes the home in place:
1067
-
1068
- - `oats session recompose --home <abs> [--dry-run] --json` → `{home, instance,
1069
- agent, soulDir, contextDir, changed, dryRun, blocks[{source,file}], previous,
1070
- note}`. Same composer spawn used, the home's own `soul` link and recorded
1071
- context/work mode. `changed:false` is a no-op (no receipt). On change the
1072
- prior text is retained as `previous` (`<home>/.oats-agents-md.<stamp>.previous`),
1073
- `instance.json` gains `instructions[]`/`recomposedAt`, and a `recomposed`
1074
- event is appended.
1075
- - **Nothing is signalled or restarted** — the harness re-reads on its own
1076
- schedule; the receipt's `note` says so. Refuses a retiring home
1077
- (`E_INSTANCE_RETIRING`), captured incarnations and capability-defined souls
1078
- (`E_UNSUPPORTED_MODE`: those are refreshed by a new resolution / package).
1079
- - **Module homes (0.25+) are `E_UNSUPPORTED_MODE` too.** A home whose
1080
- `instance.json` carries `modules{}` (spawned on a workspace deployment,
1081
- `instance-modules`) answers `E_UNSUPPORTED_MODE` ("recompose from
1082
- materialized modules is not supported yet; re-spawn"): its `AGENTS.md` was
1083
- composed from the soul at a recorded member commit plus the materialized
1084
- modules' injects, and the instance never changes under itself (decision 7).
1085
- **Desktop contract (Phase F, F4)**: for a module home, show the drift rows
1086
- and offer *re-spawn* (preview → apply of the same soul/purpose, then retire
1087
- the old instance); do not offer "recompose". A kernel recompose for module
1088
- homes is not planned — an instance never changes under itself.
1089
- The refresh path for such a home is a new spawn (the soul is re-fetched at
1090
- the member's current commit). `session-recompose` **stays advertised** in
1091
- `features[]` because the verb still works for classic homes; gate the UI
1092
- action on the feature AND on the absence of `instance.json.modules`
1093
- (`oats status --json` `instances[].modules` is non-empty for a module home),
1094
- and render the typed refusal otherwise.
1095
- - Gate on `features.includes("session-recompose")`. It is an **operator
1096
- action** (the human or the instance's parent), never something a Desktop
1097
- poll or an agent runs on itself.
1381
+ `--provider` on `spawn-provider-payload`; gate reading `teams`, `teamsSource`,
1382
+ `labels` and `warnings[]` on `teams`; gate reading `declares` on
1383
+ `settings-declared`.
1384
+
1385
+ ## Instruction refresh (`oats session recompose`) — removed in 0.26.0
1386
+
1387
+ `oats session recompose` answers `E_UNKNOWN_COMMAND`, and the
1388
+ `session-recompose` feature is no longer advertised. An instance never changes
1389
+ under itself: the refresh path is a re-spawn (preview → apply of the same
1390
+ soul/purpose, then retire the old instance), which fetches the soul at the
1391
+ member's current commit.
1098
1392
 
1099
1393
  ## Mutations exposed to Desktop v1
1100
1394
 
@@ -1105,7 +1399,7 @@ are described in [the operations contract](design/operations-contract.md).
1105
1399
 
1106
1400
  ```text
1107
1401
  oats session start --home /absolute/home [--server id] \
1108
- [--launch-config name] [--runtime pi|claude|codex] \
1402
+ [--launch-config name] [--harness pi|claude|codex] \
1109
1403
  [--model id] [--yolo|--no-yolo] --json
1110
1404
  oats session restart --home /absolute/home [the same options] --json
1111
1405
  ```
@@ -1129,7 +1423,7 @@ oats launch-config list [--dir /scope | --home /home | --soul name --agents-root
1129
1423
  oats launch-config set name --file /private/definition.json [--keep-env] --dir /scope --json
1130
1424
  oats launch-config remove name --dir /scope --json
1131
1425
  oats launch-config preview (--home /home | --soul name --agents-root /scope/agents --dir /scope) \
1132
- [--launch-config name] [--runtime runtime] [--model id] [--yolo|--no-yolo] --json
1426
+ [--launch-config name] [--harness harness] [--model id] [--yolo|--no-yolo] --json
1133
1427
  ```
1134
1428
 
1135
1429
  All accept `--server id`. Scope edits follow the registration; inspection and
@@ -1138,7 +1432,7 @@ is serialized to SSH stdin and read on the host with `--file -`; the local
1138
1432
  filename is never passed to the server as though it existed there.
1139
1433
 
1140
1434
  The list result supplies `context`, `selected` and `configurations`. Each
1141
- configuration has a name, runtime, executable, literal argument array,
1435
+ configuration has a name, harness, executable, literal argument array,
1142
1436
  environment, model, permission choice and declaring `source`. Environment
1143
1437
  literals appear as `{ "redacted": true }`; references appear as
1144
1438
  `{ "fromEnv": "VARIABLE_NAME" }`. Optional executable/model/yolo fields can be
@@ -1173,18 +1467,57 @@ See [launch configuration syntax](configuration.md) and
1173
1467
  | `warnings` | string[] | non-fatal warnings (always an array) |
1174
1468
  | `tmux` | {session,window} \| null | tmux target |
1175
1469
 
1176
- Additional informative fields: `repo`, `runtime`, `model`, `parent`,
1470
+ Additional informative fields: `repo`, `harness`, `model`, `parent`,
1177
1471
  `sibling` (explicit sibling cluster link when a root-level sibling relation
1178
1472
  was declared, else null), `relation` (`child`/`sibling`/`parent` when a
1179
1473
  relation was declared at spawn, else null), `spawnOrigin`, `attach`.
1180
1474
 
1181
- Stable error codes: `E_USAGE`, `E_NO_DEPLOYMENT`, `E_UNKNOWN_AGENT`,
1475
+ Stable error codes: `E_USAGE`, `E_LOCAL_MISSING` (no `oats-local.yaml` in reach:
1476
+ a spawn needs a workspace deployment), `E_NO_DEPLOYMENT` (the deployment's
1477
+ `agents/` root is missing), `E_SOUL_UNKNOWN`, `E_UNKNOWN_AGENT`,
1182
1478
  `E_AMBIGUOUS_SOUL`, `E_PARENT_NOT_FOUND`, `E_RELATIVE_NOT_FOUND`,
1183
1479
  `E_RELATIVE_AMBIGUOUS` (a `--relative-to`/`--parent` anchor name matches
1184
1480
  multiple team instances — disambiguate with `--relative-root <agents-root>`
1185
1481
  — or the chosen anchor is shadowed by a same-named instance so the lineage
1186
1482
  edge would resolve wrongly), `E_BAD_ARGS`,
1187
- `E_SPAWN_FAILED`.
1483
+ `E_INSTANCE_NAME_INVALID`, `E_INSTANCE_NAME_TAKEN`, `E_SPAWN_FAILED`.
1484
+
1485
+ **Instance names** (0.26.0, feature `spawn-name`). By default the name is
1486
+ derived: `<agent>-<purpose>` with `--purpose <slug>`, else `<agent>-<n>`.
1487
+ `--name <slug>` (human decision 2026-09-24) is the explicit opt-in: the
1488
+ instance name is **exactly** `<slug>`, with no `<agent>-` prefix.
1489
+
1490
+ - `--name` and `--purpose` are mutually exclusive (`E_BAD_ARGS`), and
1491
+ `--name` needs a value (`E_BAD_ARGS`).
1492
+ - The name is never rewritten. Input that is not already a slug (lowercase
1493
+ letters and digits, single dashes between them) is
1494
+ `E_INSTANCE_NAME_INVALID`, and so is a name equal to any soul name of the
1495
+ deployment (souls on the agents root, and every soul the workspace
1496
+ declares, fetched or not). Soul and instance references stay unambiguous.
1497
+ - **Instance names are at most 64 characters** (0.26.0; the tightest
1498
+ consumer is the messaging alias, which allows 1–64). This covers every
1499
+ name, explicit and derived. A longer name is `E_INSTANCE_NAME_INVALID`
1500
+ ("instance names are at most 64 characters"), in preview and apply alike,
1501
+ and is never truncated. For a derived name the refusal names the purpose
1502
+ to shorten, and the de-duplication suffix counts: when `<agent>-<purpose>`
1503
+ is taken and `<agent>-<purpose>-2` would exceed 64, the spawn is refused.
1504
+ - **Names are unique across the deployment.** An explicit name that any
1505
+ `<agents-root>/<soul>/instances/` already holds (including homes whose soul
1506
+ was since removed), or that a live window in the target tmux session carries
1507
+ (tmux backend, launched or `--no-launch`), is `E_INSTANCE_NAME_TAKEN`
1508
+ (`details.instance`, `details.home` or `details.session`). There is never a
1509
+ silent `-2` for a name the operator typed. These checks run after
1510
+ idempotency-key recovery (a keyed retry replays its receipt), and a
1511
+ concurrent spawn of another soul under the same name is caught after
1512
+ placement (see *Exclusive placement*). The invariant covers spawns through
1513
+ the CLI. Homes from earlier kernels may already share a name.
1514
+ - Derived names de-duplicate deployment-wide too (`-2`, `-3`, …), against
1515
+ every soul's instances and every soul name. Two souls never derive the same
1516
+ name (soul `a` with `--purpose b-c` against soul `a-b` with `--purpose c`).
1517
+ - `--preview` reports the final name (`instance`, `decision.instance`) and
1518
+ refuses with the same codes. The name is part of the decision revision, so
1519
+ `--expect-decision` binds it: another name under a confirmed decision is
1520
+ `E_DECISION_STALE`.
1188
1521
 
1189
1522
  Dispatch-level failures (any `--json` command): `E_UNKNOWN_COMMAND` (no
1190
1523
  kernel subcommand or capability namespace matches, or unknown capability