@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
@@ -40,7 +40,6 @@ name: release-manager # must equal the directory name
40
40
  description: Cuts, verifies and announces releases.
41
41
  work: worktree # worktree | checkout | directory | workspace
42
42
  team: engineering # optional label; else the repo's default (oats-membership.yaml); else unassigned
43
- private: true # optional: not discoverable; spawnable only from this repo
44
43
 
45
44
  capabilities: # WHERE each capability comes from — a location, never a version
46
45
  acme-release-tooling: { from: here } # here = this soul's own repo
@@ -62,16 +61,19 @@ compatibility: # optional floors on PACKAGE versions
62
61
  | Key | Meaning |
63
62
  |---|---|
64
63
  | `name`, `description`, `work` | Required. `work` is the work mode below. |
65
- | `team` | A label declared in the workspace's `teams:`; may add `defaults.byTeam` capabilities. Never gates or restricts. |
66
- | `private` | `true` keeps the soul out of workspace discovery; its own repo can still spawn it. |
64
+ | `team` | A label, or a list of labels (the first the primary), declared in the workspace's `teams:`; each may add `defaults.byTeam` capabilities and is an eligible messaging team. Never gates or restricts. |
65
+ | `private` | **Ignored since 0.26.0:** souls have no private mode. Every soul of a confirmed member is listed and spawnable; a soul that still carries the field gets a `soul-private-ignored` warning. Remove it. |
67
66
  | `capabilities` | `<cap>: { from: here \| <repo key> \| package }` or `<cap>: off`. Composed over `defaults.<slot>` ⊕ `defaults.capabilities` ⊕ `defaults.byTeam[team]`; the soul wins. |
68
67
  | `knowledge` / `messaging` / `tasks` | The slot's provider payload (true of every instance of the soul), or `none`. Merged with `oats-local.yaml` `settings.<cap>` and `spawn --provider <cap>`; the provider's `binding` contract validates the result — and refuses keys it does not declare. For `oats.okf` 2.1.3 the admitted keys are its settings (`bindings-file`, `state-dir`, `harvest-runtime`, `harvest-model`); the soul's `owns`/`reads` live in `souls/<name>/okf.json`, which OKF reads from the soul directory. |
69
68
  | `compatibility` | `<cap>: <semver range>` checked against the locked package version (`E_COMPATIBILITY`). |
70
69
 
71
70
  Schema: [`soul.schema.json`](soul.schema.json). Not in v2: `kind`, `type`,
72
- `repo`, `runtime`, `model`, `requires`, `source:`, `stores.inherit`. Model and
73
- runtime are spawn-time / launch-configuration choices (`--runtime`, `--model`,
74
- `--launch-config`), not soul identity: a soul is model-agnostic as an artifact.
71
+ `repo`, `harness`, `model`, `backend`, `yolo`, `launch-config`, `children`,
72
+ `requires`, `source:`, `stores.inherit`. Runtime, model, backend, yolo and the
73
+ launch configuration are spawn-time host choices (`--harness`, `--model`,
74
+ `--backend`, `--yolo`, `--launch-config`, or a launch configuration in
75
+ `oats-local.yaml`), not soul identity: a soul is model-agnostic as an artifact.
76
+ A child-spawn policy is a spawn flag too (`--no-child-spawns`).
75
77
 
76
78
  A soul never runs by itself. It is incarnated as an instance. Editing a soul
77
79
  is a code change, reviewed in its repo.
@@ -84,9 +86,10 @@ souls — is ordinary capability content: **`oats.core`** (package
84
86
  `defaults.capabilities: { oats.core: { from: package } }`; a soul may say
85
87
  `oats.core: off`. **`oats.setup`** (same package) carries the whole-architecture
86
88
  knowledge an onboarding expert needs. Neither is kernel magic; the kernel still
87
- composes its own instance-boundary and work-mode briefings — and, when
88
- `oats.core` resolves as a module, leaves the "You run on OATS" briefing to the
89
- module's inject (one block, not two; 0.25.2).
89
+ composes its own instance-boundary and work-mode briefings, and the "You run
90
+ on OATS" briefing is `oats.core`'s inject (the kernel ships no copy since 0.26:
91
+ a soul without `oats.core` gets no OATS operating instructions, and
92
+ `oats doctor --soul` says so).
90
93
 
91
94
  ## Instance anatomy
92
95
 
@@ -102,7 +105,6 @@ full copy** of every capability the soul resolved to:
102
105
 
103
106
  ```text
104
107
  <agents-root>/<soul>/instances/<instance>/
105
- soul → ../../soul # the soul, for reference (read-only)
106
108
  AGENTS.md # generated: soul AGENTS.md + kernel/work-mode blocks + each module's inject
107
109
  CLAUDE.md → AGENTS.md
108
110
  .agents/skills/ # canonical skill tree — soul skills + <capability>/<skill>/ full copies
@@ -111,7 +113,7 @@ full copy** of every capability the soul resolved to:
111
113
  .oats/modules/<capability>/ # the whole capability: oats.json, bin/, injects/, skills/ (hooks run from here)
112
114
  work/ # worktree, checkout symlink, attached tree, or private directory
113
115
  TASK.md # briefing and task
114
- instance.json # provenance (below)
116
+ instance.json # provenance (below); `soulDir` = the soul directory hooks get as OATS_SOUL
115
117
  STATE.md, log.md, notes/ # optional, from the knowledge capability
116
118
  ```
117
119
 
@@ -187,6 +189,11 @@ oats spawn release-manager --preview --json # decide ever
187
189
  oats spawn release-manager --provider oats.aweb identity.source=/abs/path/to/retained/.aw # instance-level payload
188
190
  ```
189
191
 
192
+ An instance is named `<soul>-<purpose>` by default, or exactly `--name <slug>`.
193
+ Names are unique per deployment (a workspace-model deployment has one agents
194
+ root): a derived name in use gets `-2`, `-3`…; an explicit `--name` in use is
195
+ refused (`E_INSTANCE_NAME_TAKEN`).
196
+
190
197
  From a deployment (where `oats-local.yaml` is), a spawn: reads the local file →
191
198
  discovers the workspace over its remotes and confirms membership → finds the
192
199
  soul among the confirmed members (or `external:`; an ambiguous bare name is
@@ -194,7 +201,7 @@ soul among the confirmed members (or `external:`; an ambiguous bare name is
194
201
  `<agents-root>/<soul>/souls/<commit12>/` at its commit (the home links that
195
202
  directory; `<agents-root>/<soul>/soul` points at the current one) → resolves
196
203
  every capability by
197
- `from:` (member = latest, package = locked + approved) → creates the home →
204
+ `from:` (member = latest, package = locked) → creates the home →
198
205
  **materializes each module whole** into `.oats/modules/` and copies its skills
199
206
  into `.agents/skills/` (a transaction: any failure leaves nothing behind) →
200
207
  composes `AGENTS.md` → records `modules`/`providers`/`workspace` in
@@ -282,9 +289,9 @@ an agent's environment variables — is operator-origin and appears top-level.
282
289
  Agents spawning sub-agents should pass `--parent "$OATS_INSTANCE"` (or the
283
290
  relation that fits).
284
291
 
285
- If the workspace has a messaging integration such as aweb, spawned instances
292
+ If the workspace has a messaging capability such as aweb, spawned instances
286
293
  can also receive identities and coordinate with each other automatically. The
287
- task layer can provide shared work state while messaging provides conversation.
294
+ tasks capability can provide shared work state while messaging provides conversation.
288
295
 
289
296
  ### Retire
290
297
 
@@ -296,13 +303,13 @@ evidence but never waits for a model or GitHub: independent processing and
296
303
  source-targeted inspection continue after the home disappears.
297
304
 
298
305
  `oats retire <instance> --self` lets an instance retire itself when the human
299
- or briefing says it is done. A live runtime cannot give a stable final
306
+ or briefing says it is done. A live harness cannot give a stable final
300
307
  inspection of its own work, so the calling process inspects, runs, and removes
301
308
  nothing: it records the intent beside its home as
302
309
  `.oats-retire-pending-<instance>.json` and starts a detached completion, then returns so the instance can report final
303
310
  status before its tmux window dies a few seconds later. The completion then
304
311
  retires the instance exactly as an external `oats retire` would: quiesce the
305
- runtime, preserve uncommitted work, run retire hooks, repair lineage, remove
312
+ harness, preserve uncommitted work, run retire hooks, repair lineage, remove
306
313
  the worktree and the home. Success leaves nothing behind: the home and the
307
314
  marker are gone. A failure writes `.oats-retired-<instance>.json` beside the
308
315
  retained home (plus the usual quarantine marker when hooks reported incomplete
@@ -316,7 +323,7 @@ follow. Every mode sits inside the same home/work boundary, which the generated
316
323
  instructions state first (`injects/instance-boundary.md`):
317
324
 
318
325
  - `<instance-home>` — the gitignored instance directory, `$OATS_INSTANCE_HOME` —
319
- holds the brain (`AGENTS.md`, `soul/`), the task, the provenance
326
+ holds the brain (the composed `AGENTS.md`; there is no soul link), the task, the provenance
320
327
  (`instance.json`) and the episodic state (`STATE.md`, `log.md`, `notes/`), and
321
328
  is where OATS operational/lifecycle commands — and the commands of whatever
322
329
  capabilities are active, `aw` among them when aweb messaging is — are run,
@@ -325,9 +332,11 @@ instructions state first (`injects/instance-boundary.md`):
325
332
  - `<instance-home>/work` — the repository or workspace view — is where
326
333
  repository reading, editing, building, testing, git and commits happen, to the
327
334
  extent the mode below permits.
328
- - The home's `soul` link is to be treated as read-only: writes through it bypass
329
- the branch and review path. Durable soul edits go through tracked paths under
330
- `work/` under the applicable review rules. OKF v2 harvest edits external
335
+ - The home has no soul link: the composed `AGENTS.md` already carries the
336
+ soul's instructions, and `instance.json` `soulDir` records the (read-only,
337
+ per-commit) soul directory every hook and dispatched command receives as
338
+ `OATS_SOUL`. Durable soul edits go through tracked paths under `work/` under
339
+ the applicable review rules. OKF v2 harvest edits external
331
340
  owned knowledge, not canonical soul files or skills.
332
341
 
333
342
  Agents move between the two as the task needs; the boundary is what each
@@ -390,7 +399,7 @@ directory; without it, the deployment directory (where `oats-local.yaml` is) is
390
399
  used. No implicit fallback changes the other modes.
391
400
 
392
401
  `--work-dir` and `--branch` are rejected. Canonical instructions, skill
393
- composition, provider trust and runtime preflight still apply. No worktree setup
402
+ composition, provider trust and harness preflight still apply. No worktree setup
394
403
  runs. Retirement preserves nonempty work in verified recovery storage beside the
395
404
  home (`workRecovery.path/work`) before deleting it, including files created by
396
405
  hooks; directory work has no disposable-root exemptions. The work-root cannot be
@@ -460,7 +469,7 @@ they are stated separately:
460
469
  compatibility aliases for the separately published pi extension.
461
470
  - **Lifecycle hooks**: `OATS_INSTANCE_HOME` and `OATS_HOME`, alongside the rest of
462
471
  the hook contract. `OATS_HOME` predates `OATS_INSTANCE_HOME` and is kept because
463
- shipped capability hooks read it; it is **not** exported to runtime sessions.
472
+ shipped capability hooks read it; it is **not** exported to harness sessions.
464
473
 
465
474
  Neither is `OATS_HOME_DIR`, which is the package store root — do not conflate
466
475
  them.
@@ -472,8 +481,7 @@ with **`E_NO_CANONICAL_ROOT`** and creates nothing.
472
481
 
473
482
  ### Deployment prerequisite: the agents directory must be operator-owned
474
483
 
475
- The canonical deployment (the agents root, `local-agents/`, and the instance
476
- homes under them) **must be owned by the operator and not writable by untrusted
484
+ The canonical deployment (the agents root and the instance homes under it) **must be owned by the operator and not writable by untrusted
477
485
  users or processes.** OATS validates resolved destinations and re-checks the home
478
486
  immediately before creating anything in it, but it cannot defeat a concurrent
479
487
  local attacker who already has write access there: Node offers no
@@ -486,33 +494,38 @@ something the kernel can close from inside.
486
494
  Default layout:
487
495
 
488
496
  ```text
489
- <scope>/
490
- agents/ # committed souls
491
- docs-expert/
492
- soul/
497
+ <deployment>/
498
+ oats-local.yaml
499
+ agents/
500
+ docs-expert/ # a workspace soul, defined in a member repository's
501
+ souls/<commit12>/ # souls/docs-expert/ and copied here per commit
493
502
  instances/
494
- local-agents/ # local souls — same shape, never committed
495
- scratch-agent/
496
- soul/
503
+ memory-harvest/ # a capability-defined agent: only instances/, no soul
497
504
  instances/
498
505
  ```
499
506
 
500
- `local-agents/` sits BESIDE `agents/` at the scope level and holds **full local
501
- souls**: complete definitions with instructions, skills, capability declarations
502
- and instances, not committed to the repo. `oats create <name> --local` creates one — the directory
503
- is created on first use, and when the scope is a git repo the kernel adds
504
- `local-agents/` to its `.gitignore` automatically. A scope with only
505
- `local-agents/` is fully operable: people can use OATS with local agents alone.
506
- Ad hoc agents from `oats spawn --instructions-file`/`--def-file` land here too.
507
- Legacy nested `agents/local-agents/` and `agents/tmp-agents/` are still read
508
- for compatibility.
509
-
510
- Instances of a local soul receive a `local-soul` briefing: work and commits
511
- are normal, but soul updates are plain file edits (nothing to commit), and
512
- durability is the machine's — promote the soul to `agents/` when it starts to
513
- matter beyond one machine. That concerns soul artifacts, not a knowledge
514
- provider's custody: a local soul using OKF v2 still reads external bases and
515
- uses PR-only delivery for any Git base.
507
+ A capability-defined agent (declared by a package or member module, such as
508
+ the OKF harvester) homes under the agents root exactly like a soul; its
509
+ directory holds only `instances/`. A name that is both a workspace soul and a
510
+ capability agent is ambiguous (`E_SOUL_AMBIGUOUS`).
511
+
512
+ There are no local souls. A soul is a member repository's `souls/<name>`
513
+ (`soul.yaml` + `AGENTS.md`); author it there and run `oats sync`. OATS 0.25
514
+ and earlier kept local souls and capability-agent homes under
515
+ `<scope>/local-agents/`: this kernel never reads, spawns into or retires from
516
+ that directory; it only detects it. Onboarding refuses into a directory that
517
+ holds one, and `oats status` and `oats doctor` report it once, as the
518
+ `legacy-local-agents` problem naming the instances found there; retire them
519
+ with the 0.25 kernel, or delete the directory once they are stopped.
520
+
521
+ A *captured home* (spawned through 0.24–0.25's captured path, removed in 0.26:
522
+ its `instance.json` records `executionBinding`, `incarnationId` or `captured`) is
523
+ reported by `oats status` and `oats doctor` as the `legacy-captured-home`
524
+ problem. It has no 0.26 runtime: start, restart, inspect/readiness/operation
525
+ `--home` and its in-home commands refuse it (`E_UNSUPPORTED_MODE`). `oats retire`
526
+ still works; it warns once per capability whose retire hook did NOT run, since
527
+ identities and memberships those capabilities created are not revoked — remove
528
+ them with the provider's own tooling. Re-spawn the soul from the deployment.
516
529
 
517
530
  Alternative agents-root layouts are planned but not built. Today the default
518
531
  layout is the only implemented layout.
@@ -8,7 +8,6 @@ contracts the kernel is built against are in
8
8
  [design/2026-09-23-workspace-module-contracts.md](design/2026-09-23-workspace-module-contracts.md);
9
9
  a full worked example (an imaginary company with three teams) is in
10
10
  [design/2026-09-23-simplified-workspace-model.md](design/2026-09-23-simplified-workspace-model.md).
11
- Moving an existing 0.24.x deployment: [rebuild-to-v2.md](rebuild-to-v2.md).
12
11
 
13
12
  ## The rule
14
13
 
@@ -19,7 +18,7 @@ versioned.**
19
18
  | Source kind | `from:` | Versioned | Trust |
20
19
  |---|---|---|---|
21
20
  | Member repo | `<repo key>` or `here` | no — always the member's **latest** default-branch state | membership (the reciprocal handshake) |
22
- | Package | `package` | yes — the version pinned in the workspace's `packages:`; `oats-lock.json` records the exact commit + integrity | executables approved **once per version**, recorded in the lock |
21
+ | Package | `package` | yes — the version pinned in the workspace's `packages:`; `oats-lock.json` records the exact commit + integrity | the declaration in `packages:` (no separate approval) |
23
22
 
24
23
  A soul names each capability **with where it comes from — a location, never a
25
24
  version**. The workspace's `packages:` says which version; materialization
@@ -39,8 +38,8 @@ machine (`oats-local.yaml`); the lock (`oats-lock.json`) sits beside
39
38
  `oats-local.yaml` and is identical on every machine that synced the same
40
39
  workspace commit. Schemas: [`oats-workspace.schema.json`](oats-workspace.schema.json),
41
40
  [`oats-membership.schema.json`](oats-membership.schema.json),
42
- [`soul.schema.json`](soul.schema.json), [`oats-local.schema.json`](oats-local.schema.json),
43
- [`oats-lock-v3.schema.json`](oats-lock-v3.schema.json). The JSON schemas encode
41
+ [`soul.schema.json`](soul.schema.json), [`oats-local.schema.json`](oats-local.schema.json);
42
+ the lock's format is in [packages](packages.md#lock-v3). The JSON schemas encode
44
43
  shapes; domain rules (declared teams, duplicate members, canonical `from:` keys,
45
44
  the two `packages:` value forms) live in the kernel's `validateWorkspace` /
46
45
  `validateSoul`, which are the authority.
@@ -61,7 +60,7 @@ members: # repo refs, NO @revision (E_WORKSPAC
61
60
 
62
61
  packages: # the ONLY versioned things
63
62
  oats.framework: v1.1.3 # bare version → resolves through the official catalog
64
- oats.okf: v2.1.4
63
+ oats.okf: v2.1.5
65
64
  acme.tools: git:github.com/acme/tools@v0.4.0 # outside the catalog → git:<repo>@<tag|OID>; still a package
66
65
 
67
66
  teams: # labels, declared once so they cannot drift
@@ -141,8 +140,9 @@ declaration; it travels with the soul into the per-commit soul cache. The
141
140
  carry only the binding's settings keys (`bindings-file`, `state-dir`,
142
141
  `harvest-runtime`, `harvest-model`); `owns`/`reads`/`root` there are refused by
143
142
  the provider, not read (a soul payload grammar is an OKF follow-up). Every
144
- `souls/*/soul.yaml` in a member is discoverable; one that wants to stay
145
- internal says `private: true` (spawnable only from its own repo). A soul's
143
+ `souls/*/soul.yaml` in a member is listed and spawnable — souls have no
144
+ private mode (`private:` in a soul.yaml is ignored since 0.26.0, with a
145
+ `soul-private-ignored` warning). A soul's
146
146
  `name` must equal its directory name; the first of two souls declaring one
147
147
  name (by path) is listed, the second is a problem.
148
148
 
@@ -151,7 +151,8 @@ name (by path) is listed, the second is a problem.
151
151
  The capability manifest is the one file that did not change (see
152
152
  [capabilities.md](capabilities.md)). Discovery relies on `capability` (the same
153
153
  `^[a-z0-9][a-z0-9._-]*$` grammar every `capabilities:` key uses), `version`,
154
- `layer`, and may read `private: true` and `team: <label>`. `version` is
154
+ `layer`, and may read `private: true` (a **repo-owned** capability) and
155
+ `team: <label>`. `version` is
155
156
  informational for member capabilities — a materialized copy is identified by
156
157
  its content digest.
157
158
 
@@ -188,7 +189,14 @@ with the right name, is not admission.
188
189
  model as a repo's committed `.agents/skills/`: whoever can push to the repo
189
190
  decides what runs, and the branch's latest state is what runs. No per-operator
190
191
  trust lists, no per-capability approval for members. Packages come from
191
- *outside* that boundary and keep a one-time executable approval per version.
192
+ *outside* that boundary, and **declaring one in the workspace's `packages:` is
193
+ the trust decision** (human decision, 2026-09-24): people install a package only
194
+ when they trust it, so there is no second, per-version approval step. The lock
195
+ is reproducibility, not approval — it pins the exact commit and content
196
+ integrity, and `oats sync` refuses drift (a moved tag, changed content, an
197
+ edited capability list). A spawn admits only a locked package the workspace
198
+ **still declares**: one removed from `packages:` but left in a stale lock is
199
+ `E_PACKAGE_MISSING { reason: "undeclared" }` until `oats sync` drops it.
192
200
 
193
201
  **The handshake is observed with the operator's own Git read access, in one
194
202
  access context.** The kernel reads both halves over the remotes
@@ -210,10 +218,11 @@ its capabilities unresolvable (`E_NOT_A_MEMBER` / `E_MEMBERSHIP_UNCONFIRMED`).
210
218
  Reading the workspace repo *is* being in the workspace — a workspace's access
211
219
  control is Git's.
212
220
 
213
- **Private items.** `private: true` on a soul or a capability keeps it out of the
214
- workspace listing; a private capability is usable only by souls of the same
215
- repo (`E_CAPABILITY_PRIVATE` otherwise). Owners still see their own private
216
- items.
221
+ **Repo-owned capabilities.** `private: true` in a capability's `oats.json`
222
+ makes it **repo-owned**: it is listed like any other capability (with
223
+ `private: true`; the human table marks it "(repo-owned)"), and it is usable
224
+ only by souls of the same repo (`E_CAPABILITY_PRIVATE` otherwise). Souls have
225
+ no private mode: every soul of a confirmed member is listed and spawnable.
217
226
 
218
227
  **External souls.** `external:` adopts a soul by reference from a repo that is
219
228
  **not** a member, pinned to a full commit. No handshake is asked for and none is
@@ -226,7 +235,7 @@ its slots. An `external[].team` overrides the soul's own `team`.
226
235
  A repository may be a **member** (it completed the handshake; its `souls/*` and
227
236
  `capabilities/*` are member-tier: latest state, trusted by membership) **and** a
228
237
  **package publisher** (its `oats-package/` is consumed only through
229
- `packages:`: versioned, locked, approved). The two never collapse:
238
+ `packages:`: versioned and locked). The two never collapse:
230
239
 
231
240
  - `from: <repo key>` looks **only** under `<repo>/capabilities/<name>/oats.json`
232
241
  at the member's latest state. It never looks inside `oats-package/`. A name
@@ -244,31 +253,32 @@ So the framework's own souls say `oats.okf: { from: package }` even though
244
253
  member soul that is the expert in that capability (`okf-expert`, `aweb-expert`,
245
254
  …), discoverable at latest state like any member soul.
246
255
 
247
- ## Packages, lock, approval, catalog
256
+ ## Packages, lock, catalog
248
257
 
249
258
  `packages:` values have exactly two forms:
250
259
 
251
260
  - a **bare version** (`v2.1.3`, `2.1.3`, `v1.0.0-rc.1`) — resolved through the
252
261
  official catalog (`package-catalog.json` in the `oats` repo; the reviewed
253
- marketplace, see [official-marketplace.md](official-marketplace.md)). This is
262
+ list, see [official-catalog.md](official-catalog.md)). This is
254
263
  the only way a package becomes *pinnable by id*.
255
264
  - **`git:<repo>@<ref>`** — a direct package ref: `<repo>` is any ref the kernel
256
265
  understands (`github.com/org/repo`, `https://…`, `git@host:…`, `/abs/bare.git`,
257
266
  `file:///…`), `<ref>` a tag name or a full commit OID. The package is read at
258
267
  `oats-package/` inside that repo.
259
268
 
260
- Both are packages: versioned, locked, approved. A ref that resolves to a
261
- **branch** is refused (`E_PACKAGE_INTEGRITY { why: "branch" }`) — versions are
262
- immutable. A tag that moved (same version string, different commit) fails
263
- integrity on the next `oats sync` and asks again.
264
-
265
- `oats sync` confirms membership, resolves every `packages:` entry to a commit +
266
- content digest, asks (on a terminal) for any missing per-version executable
267
- approval — or takes it from repeatable `--approve <id>@<version>` flags for
268
- unattended runs (each approves exactly the entry the resolution contains; the
269
- digest is always computed, never typed; Ctrl+D at the prompt is a decline,
270
- exit `2`) — writes `oats-lock.json` (lockfileVersion 3), creates `agents/` if
271
- absent and reports what changed. `oats package add <id> <version|git:…@…>` / `oats package remove <id>` edit
269
+ Both are packages: versioned and locked. A ref that resolves to a **branch** is
270
+ refused (`E_PACKAGE_INTEGRITY { why: "branch" }`) — versions are immutable. A
271
+ tag that moved (same version string, different commit), or content that no
272
+ longer matches the locked integrity, fails with `E_PACKAGE_INTEGRITY` on the
273
+ next `oats sync`.
274
+
275
+ **There is no package approval** (human decision, 2026-09-24). Declaring a
276
+ package in `packages:` is the trust decision; `oats sync` asks nothing and
277
+ `--approve` is `E_BAD_ARGS`. `oats sync` confirms membership, resolves every
278
+ `packages:` entry to a commit + content digest, writes `oats-lock.json`
279
+ (lockfileVersion 3), creates `agents/` if absent, reports what changed and
280
+ exits `0`. A lock written by an earlier kernel may still carry an `approved`
281
+ record per entry: it is ignored, and the next write drops it. `oats package add <id> <version|git:…@…>` / `oats package remove <id>` edit
272
282
  `packages:` in the workspace file when it is tracked by the current checkout,
273
283
  else print the line to add — the workspace file is shared through Git. Details:
274
284
  [packages.md](packages.md).
@@ -282,8 +292,8 @@ the workspace's slot default):
282
292
 
283
293
  ```
284
294
  from: package → some locked package provides `name` else E_PACKAGE_MISSING (run `oats sync`)
285
- → that package version is approved else E_PACKAGE_UNAPPROVED
286
- → read its capability manifest at the locked commit; copy; record package/version/commit/digest
295
+ → read its manifests at the locked commit; the lock's capability list must match else E_PACKAGE_INTEGRITY
296
+ → copy; record package/version/commit/digest
287
297
  from: <repo> → <repo> is a CONFIRMED member else E_NOT_A_MEMBER / E_MEMBERSHIP_UNCONFIRMED
288
298
  (or `here`) → it has capabilities/<name>/oats.json else E_CAPABILITY_MISSING
289
299
  → not private, unless <repo> is the soul's own else E_CAPABILITY_PRIVATE
@@ -308,13 +318,12 @@ Nothing is symlinked, nothing is shared between instances.
308
318
  ```
309
319
  <agents-root>/<soul>/instances/<instance>/
310
320
  ├── AGENTS.md # composed: soul AGENTS.md + kernel/work-mode blocks + each module's inject
311
- │ # (with oats.core resolved, the module's "You run on OATS" block is the only one — the kernel's legacy copy is suppressed)
321
+ │ # (the "You run on OATS" block is oats.core's inject; the kernel ships no copy)
312
322
  ├── CLAUDE.md → AGENTS.md
313
323
  ├── .agents/skills/<capability>/<skill>/SKILL.md # full copies; where pi/codex look
314
324
  ├── .claude/skills → ../.agents/skills
315
325
  ├── .oats/modules/<capability>/ # the full capability copy: oats.json, bin/, injects/, skills/
316
326
  ├── instance.json # modules{}, providers{}, workspace{} recorded here
317
- ├── soul → <agents-root>/<soul>/soul # read-only reference
318
327
  ├── TASK.md
319
328
  └── work/
320
329
  ```
@@ -347,10 +356,16 @@ not exclude anything.
347
356
  ## Teams
348
357
 
349
358
  `teams:` declares labels once (`global`, `engineering`, …) so they cannot drift
350
- into typos. A soul or capability carries `team:`, else its repo's default from
351
- `oats-membership.yaml`, else `unassigned`. A label not declared in `teams:` is
352
- `E_TEAM_UNKNOWN` (the item is still listed). `defaults.byTeam.<team>.capabilities`
353
- adds capabilities additively for souls with that label (`off` removes). **A
359
+ into typos. A soul carries `team:` — one label or a list (`team: [engineering,
360
+ reviewers]`, the first the primary) — else its repo's default from
361
+ `oats-membership.yaml` (same shape), else `unassigned`; a capability carries one
362
+ label. A label not declared in `teams:` is `E_TEAM_UNKNOWN` (the item is still
363
+ listed); a declared label without a `messaging.byTeam` entry is the
364
+ `unmapped-team-label` warning. `defaults.byTeam.<team>.capabilities` adds
365
+ capabilities additively for souls with that label, for each label in order
366
+ (`off` removes; two labels that disagree are `E_TEAM_CONFLICT`). Each label is
367
+ an *eligible* messaging team the provider may join on request — see
368
+ [capabilities.md](capabilities.md#several-team-labels). **A
354
369
  label never gates, restricts, changes trust or partitions the knowledge
355
370
  store** — it organises and can supply defaults. The messaging provider's payload
356
371
  (private teams, channels) lives under `messaging:`, so "team" means one thing.
@@ -364,9 +379,13 @@ store** — it organises and can supply defaults. The messaging provider's paylo
364
379
  | A fact about **this spawn** | `oats spawn … --provider <cap> key=value` (repeatable; dotted keys nest) → `instance.json.providers.<cap>` | `--provider oats.aweb identity.source=/abs/path/to/retained/.aw` |
365
380
 
366
381
  The merged payload is `workspace.messaging` (messaging slot only; its base
367
- keys ⊕ `byTeam[<soul's team>]`, with `byTeam` itself stripped) ⊕ soul slot
368
- payload ⊕ `local.settings[cap]` ⊕ `spawn.providers[cap]` — objects deep-merge,
369
- later wins on scalars and arrays. The provider's own `binding` contract
382
+ keys, with `byTeam` stripped) ⊕ soul slot payload ⊕ `local.settings[cap]` ⊕
383
+ `spawn.providers[cap]` — objects deep-merge, later wins on scalars and arrays.
384
+ **No `byTeam[<label>]` is merged into it, the primary's included** (teams
385
+ amendment K): each label's `base ⊕ byTeam[label]` reaches the provider only as
386
+ that label's entry in `OATS_TEAMS` (the preview's `teams`). So `settings.team`
387
+ (and `OATS_TEAM_ID`) is the personal team if the host, the soul or the spawn
388
+ set one; empty means the provider's own default. The provider's own `binding` contract
370
389
  (`normalize → bind → check`) runs over the merged payload exactly as before.
371
390
  Two teams, two messaging identities, one workspace:
372
391
 
@@ -378,18 +397,21 @@ messaging:
378
397
  cloud: { team: aweb:example.cloud }
379
398
  ```
380
399
 
381
- A soul with `team: cloud` hands its messaging provider `{ team: aweb:example.cloud, … }`;
382
- a label under `byTeam` that is not declared in `teams:` is `E_WORKSPACE_SCHEMA`.
383
- **`byTeam` is kernel-merged; whether a provider honours what arrives is the
384
- provider's.** `spawn --preview` shows the merged `settings.<cap>` so the
385
- delivery is verifiable, and `instance.json.providers.<cap>` records it — but
386
- **oats.aweb 1.12.0 reads `team` from its payload** and mints into exactly that
387
- team (`--team-id`), warning when the payload disagrees with an `OATS_TEAM_*`
388
- value — so `byTeam.<label>.team` IS the per-label identity. What the payload
389
- does not change is **where the `.aw` root is found**: the hook still searches
390
- the bounded candidates in
391
- [rebuild-to-v2.md §8b](rebuild-to-v2.md#8b-where-the-team-aw-lives-now-and-what-byteam-does-today)
392
- and that root must hold a membership of the named team (the deployment's `.aw`
400
+ A soul with `team: cloud` hands its messaging provider the eligible team
401
+ `{ label: cloud, team: aweb:example.cloud, mapped: true, payload: { team: aweb:example.cloud, … } }`
402
+ in `OATS_TEAMS`; its settings carry no `team` unless the host, soul or spawn set
403
+ one. A label under `byTeam` that is not declared in `teams:` is
404
+ `E_WORKSPACE_SCHEMA`. **Joining an eligible team is the provider's explicit
405
+ act.** `spawn --preview` shows the merged `settings.<cap>` and the `teams`, so
406
+ the delivery is verifiable, and `instance.json` records both. With oats.aweb
407
+ 1.13.1 (which reads `team` from its settings and ignores `OATS_TEAMS`) the
408
+ primary identity therefore mints into the personal team: the `.aw` root's
409
+ active team, or the one the host set. What the payload does not change is
410
+ **where the `.aw` root is found**: the hook still searches
411
+ bounded candidates, first hit wins — the instance home, the Git repository
412
+ containing it, the soul's work repository and the Git repository containing
413
+ it, then the deployment directory (`OATS_WORKSPACE`); never the user home or
414
+ above the deployment — and that root must hold a membership of the named team (the deployment's `.aw`
393
415
  joined to every team its labels name is the simple layout). On oats.aweb
394
416
  1.11.2 `team` was ignored (the root's active team won), so `byTeam` there is
395
417
  a recorded intent only.
@@ -428,7 +450,7 @@ The deployment directory is **yours to choose** (decision 9) — an existing fol
428
450
  ```
429
451
  ~/acme/ ← the directory you chose
430
452
  ├── oats-local.yaml ← which workspace this machine realizes + host paths + disabled souls
431
- ├── oats-lock.json ← exact commit + integrity + per-version approval per package
453
+ ├── oats-lock.json ← exact commit + integrity per package
432
454
  ├── agents/ ← instance homes (each self-contained) + fetched soul sources
433
455
  ├── platform/ ← clone of github.com/acme/platform (only if someone works IN it; may live elsewhere — see clones:)
434
456
  └── tools/
@@ -454,8 +476,8 @@ the host repo readable (it holds declarations, no secrets) or grant access.
454
476
 
455
477
  Two things keep the standalone spawn useful rather than hollow: `oats.core`
456
478
  (the framework's own operational package) is the kernel's default here as
457
- well, resolved from the official catalog through the operator's own lock and
458
- approved like any package (a soul may say `oats.core: off`); and the
479
+ well, resolved from the official catalog through the operator's own lock like any
480
+ package (a soul may say `oats.core: off`); and the
459
481
  operator's `oats-local.yaml` may name the repo directly (`workspace: <member
460
482
  ref>` — the kernel notices it is a member whose workspace it cannot read and
461
483
  falls back to the standalone view — or `standalone: <repo ref>` to ask for
@@ -467,8 +489,8 @@ an explicit `standalone:` header its next steps say so and name that one repo.
467
489
  member capability's hooks and command scripts run on every operator's machine at
468
490
  spawn, gated by nothing but the handshake. In a mixed public/private
469
491
  organisation keep **souls only** in public members and let executable
470
- capabilities come from packages (approved per version in the lock) or from
471
- private members.
492
+ capabilities come from packages (declared in `packages:`, pinned by the lock)
493
+ or from private members.
472
494
 
473
495
  **Hosting the workspace file when some members are private.** Everyone who
474
496
  can read the workspace file sees the member list. So: a public member never
@@ -502,12 +524,12 @@ installed-capability tier (`.agents/capabilities/installed/`) and
502
524
  `E_UNKNOWN_COMMAND` naming its replacement); per-soul `stores.<x>.inherit`;
503
525
  ambient-skill exclusion at launch. Lock v1/v2 files are `E_LOCK_SCHEMA`.
504
526
  There is no converter and no dual-schema reader: a 0.24.x kernel keeps
505
- spawning 0.24.x deployments; see [rebuild-to-v2.md](rebuild-to-v2.md).
527
+ spawning 0.24.x deployments.
506
528
 
507
529
  ## Related
508
530
 
509
531
  - [Souls and instances](souls-and-instances.md) · [Packages](packages.md) ·
510
- [Configuration (`oats-local.yaml`)](configuration.md) · [Rebuild guide](rebuild-to-v2.md)
532
+ [Configuration (`oats-local.yaml`)](configuration.md)
511
533
  - [Capability manifests](capabilities.md) · [Contracts](layers.md) ·
512
534
  [Desktop CLI API — workspace model](desktop-cli-api.md#workspace-model-workspaceapi-2)
513
535
  - [Design navigation](design/README.md)
@@ -1,7 +1,7 @@
1
1
  ## Your two directories
2
2
 
3
3
  **`<instance-home>` is where this session starts** — the specific gitignored OATS
4
- instance directory you woke up in, given to your runtime and to every lifecycle
4
+ instance directory you woke up in, given to your harness and to every lifecycle
5
5
  hook as `$OATS_INSTANCE_HOME`. It is not your user home (`~`), not the repository
6
6
  root, and not the work tree. Anything that says "your home" means this directory.
7
7
 
@@ -20,7 +20,7 @@ root, and not the work tree. Anything that says "your home" means this directory
20
20
  - **Soul work is repository work.** If your TASK is to change soul content that
21
21
  lives in this repository, that is ordinary code work — do it on tracked paths
22
22
  under `work/`, reviewed like the rest. How your own learnings reach your soul
23
- is your knowledge layer's business, and its instructions below say so if you
23
+ is your knowledge capability's business, and its instructions below say so if you
24
24
  have one.
25
25
 
26
26
  **`<instance-home>/work` is your repository or workspace view** — whatever your
@@ -8,7 +8,7 @@ their branch and their uncommitted state. You are a guest in their workspace.
8
8
  - Keep your changes and commits **small and clearly attributable** (your
9
9
  instance name in commit messages where ambiguity is possible).
10
10
  - Do not touch files the owner is mid-editing unless your task says so; when
11
- in doubt, coordinate through your messaging layer or your spawner.
11
+ in doubt, coordinate through your messaging capability or your spawner.
12
12
  - Retiring you never removes the shared tree — cleanup of the tree is the
13
13
  owner's concern, not yours.
14
14
 
@@ -6,8 +6,8 @@ cross-repo coordinator: your product is routing, analysis, and coordination —
6
6
  not code changes.
7
7
 
8
8
  - **Read freely across all member repos; never edit or commit inside them.**
9
- Repo changes are routed to that repo's own agents (see `oats status --team`,
10
- your task layer, or messaging) or to the human.
9
+ Repo changes are routed to that repo's own agents (see `oats status` in the
10
+ deployment, your tasks capability, or messaging) or to the human.
11
11
  - No git state operations in any member repo: no branch switching, no
12
12
  commits, no worktrees, no resets.
13
13
  - Your own working state lives in your instance home, not in any member repo,
@@ -1,4 +1,4 @@
1
- /** Bounded descriptor-backed reads shared by private portable metadata stores. */
1
+ /** Bounded descriptor-backed reads of kernel-owned files (manifests, instance records). */
2
2
  import { constants, closeSync, fstatSync, lstatSync, openSync, readSync } from "node:fs";
3
3
  import { oatsError } from "./errors.mjs";
4
4
 
@@ -8,19 +8,19 @@ export function readPortableBytes(path, { missingCode = "resource-not-found", in
8
8
  catch (error) {
9
9
  if (error.code !== "ENOENT") throw error;
10
10
  if (allowMissing) return null;
11
- throw oatsError(missingCode, "portable metadata is absent");
11
+ throw oatsError(missingCode, "metadata file is absent");
12
12
  }
13
- if (!stat.isFile()) throw oatsError(invalidCode, "portable metadata must be a regular file");
14
- if (stat.size > 8 * 1024 * 1024) throw oatsError("resource-limit", "portable metadata byte limit exceeded");
13
+ if (!stat.isFile()) throw oatsError(invalidCode, "metadata file must be a regular file");
14
+ if (stat.size > 8 * 1024 * 1024) throw oatsError("resource-limit", "metadata file byte limit exceeded");
15
15
  const fd = openSync(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
16
16
  try {
17
17
  const before = fstatSync(fd);
18
- if (!before.isFile() || before.dev !== stat.dev || before.ino !== stat.ino || before.size !== stat.size) throw oatsError("integrity-drift", "portable metadata changed during opening");
18
+ if (!before.isFile() || before.dev !== stat.dev || before.ino !== stat.ino || before.size !== stat.size) throw oatsError("integrity-drift", "metadata file changed during opening");
19
19
  const buffer = Buffer.alloc(stat.size + 1);
20
20
  let count = 0, size;
21
21
  while (count < buffer.length && (size = readSync(fd, buffer, count, buffer.length - count, null)) > 0) count += size;
22
22
  const after = fstatSync(fd);
23
- if (count !== before.size || after.size !== before.size || after.mtimeMs !== before.mtimeMs || after.ctimeMs !== before.ctimeMs) throw oatsError("integrity-drift", "portable metadata changed during reading");
23
+ if (count !== before.size || after.size !== before.size || after.mtimeMs !== before.mtimeMs || after.ctimeMs !== before.ctimeMs) throw oatsError("integrity-drift", "metadata file changed during reading");
24
24
  return buffer.subarray(0, count);
25
25
  } finally { closeSync(fd); }
26
26
  }
@@ -1,5 +1,6 @@
1
- /** Bounded data codecs shared by portable declarations and retained records.
2
- * No source resolution, filesystem access, getters, or caller serialization hooks. */
1
+ /** Bounded data codecs: strict JSON decoding and canonical JSON (config documents,
2
+ * instance records, provider answers). No source resolution, filesystem access,
3
+ * getters, or caller serialization hooks. */
3
4
  import { oatsError } from "./errors.mjs";
4
5
 
5
6
  const DEFAULTS = Object.freeze({ maxBytes: 8 * 1024 * 1024, maxDepth: 64, maxEntries: 100_000 });
@@ -39,16 +40,6 @@ export function decodeUtf8(input, where = "input") {
39
40
  }
40
41
  export const compareUtf8 = (a, b) => Buffer.compare(Buffer.from(a, "utf8"), Buffer.from(b, "utf8"));
41
42
 
42
- /** Freeze a bounded JSON observation, never caller accessors or cyclic objects. */
43
- export function freezeJson(value) {
44
- canonicalJson(value);
45
- const seen = new WeakSet();
46
- const freeze = (item) => {
47
- if (!item || typeof item !== "object" || seen.has(item)) return;
48
- seen.add(item); for (const child of Object.values(item)) freeze(child); Object.freeze(item);
49
- };
50
- freeze(value); return value;
51
- }
52
43
 
53
44
  /** Canonical JSON includes exactly one final LF. Emit sorted keys directly:
54
45
  * JSON.stringify on a rebuilt object would reorder integer-looking keys. */