@awebai/oats 0.25.1 → 0.25.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/oats.mjs +151 -35
- package/docs/configuration.md +3 -3
- package/docs/design/2026-09-20-redesign-program-board.md +1 -1
- package/docs/design/2026-09-20-workspace-and-portable-adoption-plan.md +1 -1
- package/docs/design/2026-09-23-simplified-workspace-model.md +4 -4
- package/docs/design/2026-09-23-workspace-module-contracts.md +108 -2
- package/docs/design/2026-09-23-workspace-v2-implementation-plan.md +3 -3
- package/docs/desktop-cli-api.md +1 -1
- package/docs/desktop.md +1 -1
- package/docs/first-team.md +3 -3
- package/docs/knowledge.md +14 -7
- package/docs/rebuild-to-v2.md +182 -26
- package/docs/release-notes/v0.25.2.md +80 -0
- package/docs/souls-and-instances.md +34 -16
- package/docs/workspaces.md +65 -26
- package/lib/core.mjs +33 -4
- package/lib/instance-resolution.mjs +132 -2
- package/lib/materialize.mjs +29 -0
- package/package.json +1 -1
package/docs/desktop-cli-api.md
CHANGED
|
@@ -748,7 +748,7 @@ Every `commit` is a full 40-hex OID; every digest is `sha256-<hex>`; every
|
|
|
748
748
|
`oats onboard [<dir>] --workspace <repo ref> [--json]`
|
|
749
749
|
|
|
750
750
|
The **bootstrap** of a deployment (decision 9): realizes a workspace on this
|
|
751
|
-
machine in the
|
|
751
|
+
machine in the directory the operator chooses (any existing folder). It writes
|
|
752
752
|
`<dir>/oats-local.yaml` (`{ schemaVersion: 2, workspace: <ref> }`), creates
|
|
753
753
|
`<dir>/agents/` (the instance homes), then runs exactly the `oats sync` body
|
|
754
754
|
over the directory just written — discover over the remotes, confirm
|
package/docs/desktop.md
CHANGED
|
@@ -86,7 +86,7 @@ The app starts on the directory it was launched with (its own folder by
|
|
|
86
86
|
default). To view a deployment, open the workspace switcher in the sidebar
|
|
87
87
|
and choose **Add workspace → Browse**, then point it at an OATS deployment —
|
|
88
88
|
a directory containing `agents/` (under the 0.25 workspace model that is the
|
|
89
|
-
|
|
89
|
+
deployment directory (the operator's choice) holding `oats-local.yaml` and `agents/`;
|
|
90
90
|
under 0.24, an `agents/` root, a `local-agents/` root for machine-local souls,
|
|
91
91
|
or a team scope whose `oats-config.yaml` declares `team:`). *The Desktop's own
|
|
92
92
|
multi-repo roster ("team scopes show every member repo's agents under one
|
package/docs/first-team.md
CHANGED
|
@@ -56,15 +56,15 @@ one soul and, optionally, one capability. Every soul gets `oats.core` from the
|
|
|
56
56
|
the resulting layout).
|
|
57
57
|
|
|
58
58
|
```bash
|
|
59
|
-
oats onboard ~/acme
|
|
59
|
+
oats onboard ~/acme --workspace git:github.com/acme/agents # any directory — an existing one with your clones is fine
|
|
60
60
|
```
|
|
61
61
|
|
|
62
62
|
```
|
|
63
|
-
~/acme
|
|
63
|
+
~/acme/ # the directory you chose; these three entries are what the kernel needs
|
|
64
64
|
├── oats-local.yaml # { schemaVersion: 2, workspace: git:github.com/acme/agents }
|
|
65
65
|
├── oats-lock.json # lockfileVersion 3: commit + integrity + approval per package
|
|
66
66
|
├── agents/ # instance homes
|
|
67
|
-
└── <member>/ # clones of the members you work IN
|
|
67
|
+
└── <member>/ # clones of the members you work IN — here or anywhere named in oats-local.yaml clones:
|
|
68
68
|
```
|
|
69
69
|
|
|
70
70
|
Read the report it prints: every member row must be `✓↔` (confirmed) — fix
|
package/docs/knowledge.md
CHANGED
|
@@ -66,9 +66,11 @@ packages:
|
|
|
66
66
|
defaults:
|
|
67
67
|
knowledge: { oats.okf: { from: package } }
|
|
68
68
|
|
|
69
|
-
# souls/domain-expert/soul.yaml
|
|
69
|
+
# souls/domain-expert/soul.yaml — nothing under knowledge: for oats.okf; the default fills the slot.
|
|
70
|
+
# What the soul owns/reads is souls/domain-expert/okf.json (below), not a soul.yaml payload.
|
|
71
|
+
# A soul-true binding setting is the one thing the payload may carry, e.g.:
|
|
70
72
|
knowledge:
|
|
71
|
-
|
|
73
|
+
harvest-runtime: claude
|
|
72
74
|
|
|
73
75
|
# oats-local.yaml (this machine)
|
|
74
76
|
settings:
|
|
@@ -79,11 +81,14 @@ settings:
|
|
|
79
81
|
|
|
80
82
|
```bash
|
|
81
83
|
oats sync # resolves v2.1.3 to a commit, asks executable approval once
|
|
82
|
-
oats spawn domain-expert --preview --json # the exact oats.okf module (package, version, commit)
|
|
84
|
+
oats spawn domain-expert --preview --json # the exact oats.okf module (package, version, commit) + settings.oats.okf (the merged payload)
|
|
83
85
|
```
|
|
84
86
|
|
|
85
|
-
Pinning activates nothing by itself: the soul's `
|
|
86
|
-
|
|
87
|
+
Pinning activates nothing by itself: the soul's `okf.json` must exist and the
|
|
88
|
+
merged payload (soul `knowledge:` ⊕ `settings.oats.okf` ⊕ `--provider`) must be
|
|
89
|
+
bindable — it may carry **only** the four settings below (`bindings-file`,
|
|
90
|
+
`state-dir`, `harvest-runtime`, `harvest-model`); `owns`/`reads`/`root` on the
|
|
91
|
+
soul payload are refused by 2.1.3, not read. The lock stays exact until the
|
|
87
92
|
workspace bumps `packages.oats.okf`; v1 operators must plan migration before
|
|
88
93
|
that bump. Executable changes come with a new version and a new approval. A
|
|
89
94
|
service worker need not itself fill the knowledge slot (`knowledge: none`).
|
|
@@ -129,8 +134,10 @@ directory, not the current working directory:
|
|
|
129
134
|
source homes/worktrees; keep the bindings file outside state and bases. Local
|
|
130
135
|
Git locators must not be disposable linked worktrees. Directory lock/journal
|
|
131
136
|
artifacts also must not overlap state, sources or another base.
|
|
132
|
-
- Settings are `bindings-file`, `
|
|
133
|
-
|
|
137
|
+
- Settings are `bindings-file`, `state-dir` (both required, absolute host
|
|
138
|
+
paths), `harvest-runtime` (`pi`, `claude`, `codex`, default `pi`), and
|
|
139
|
+
optional `harvest-model` — the complete list a 2.1.3 payload may carry.
|
|
140
|
+
Choose an installed, authenticated
|
|
134
141
|
worker runtime independently of the source; omitted models use that runtime's
|
|
135
142
|
configured default. V1 record-window settings are not v2 settings.
|
|
136
143
|
|
package/docs/rebuild-to-v2.md
CHANGED
|
@@ -48,6 +48,8 @@ capabilities plus `oats.core`), so a public soul stays usable.
|
|
|
48
48
|
Two teams that need two different messaging identities (an open-source team
|
|
49
49
|
and a hosted-operations team, say) stay in ONE workspace: `team:` is a label,
|
|
50
50
|
and the provider payload is addressed by label under `messaging.byTeam` (§2).
|
|
51
|
+
Read §8b before relying on it: the kernel merges `byTeam`, but oats.aweb 1.11.2
|
|
52
|
+
does not yet read the `team` it delivers.
|
|
51
53
|
|
|
52
54
|
## 2. Write `oats-workspace.yaml` v2 in the host repo
|
|
53
55
|
|
|
@@ -141,7 +143,7 @@ they enumerate `souls/*/soul.yaml`. A soul left under `agents/` is invisible to
|
|
|
141
143
|
| `stores.inherit` | delete (stores are declared once in the workspace) |
|
|
142
144
|
| `imports` | delete |
|
|
143
145
|
| `kind`, `type`, `repo`, `runtime`, `model`, `launch-config` | delete — model/runtime/launch config are spawn-time choices; `team:` replaces `type:` as the grouping |
|
|
144
|
-
| `knowledge:` / `messaging:` payload | keep as is (opaque provider payload); `none` empties the slot |
|
|
146
|
+
| `knowledge:` / `messaging:` payload | keep as is (opaque provider payload); `none` empties the slot. **For `oats.okf` see the box below: the payload is the binding's SETTINGS keys only; what the soul owns/reads stays in `okf.json`** |
|
|
145
147
|
| — | `compatibility: { <cap>: ">=x.y" }` if you want a floor |
|
|
146
148
|
|
|
147
149
|
```yaml
|
|
@@ -152,9 +154,8 @@ work: worktree
|
|
|
152
154
|
team: engineering
|
|
153
155
|
capabilities:
|
|
154
156
|
acme-release-tooling: { from: here }
|
|
155
|
-
knowledge:
|
|
156
|
-
|
|
157
|
-
reads: [platform-engineer]
|
|
157
|
+
# knowledge: — nothing here for oats.okf: the workspace default fills the slot and
|
|
158
|
+
# souls/release-manager/okf.json (below) says what this soul owns and reads.
|
|
158
159
|
messaging:
|
|
159
160
|
channels: [acme-eng]
|
|
160
161
|
```
|
|
@@ -164,6 +165,48 @@ are required. Capabilities the repo exports live at
|
|
|
164
165
|
`capabilities/<name>/oats.json` — the manifest is unchanged; you may add
|
|
165
166
|
`private: true` / `team:`.
|
|
166
167
|
|
|
168
|
+
**`oats.okf` 2.1.3 reads `souls/<name>/okf.json`, not a `knowledge:` payload.**
|
|
169
|
+
Earlier drafts of this guide showed `knowledge: { owns: …, reads: … }` or
|
|
170
|
+
`knowledge: { store, root }` on the soul; **no shipped provider consumes those
|
|
171
|
+
keys**. What OKF 2.1.3 actually reads at spawn is two things:
|
|
172
|
+
|
|
173
|
+
1. **`<soul>/okf.json`** (travels with the soul, fetched into the per-commit
|
|
174
|
+
soul cache like `AGENTS.md`) — the soul's knowledge declaration, exactly
|
|
175
|
+
these keys and no others:
|
|
176
|
+
|
|
177
|
+
```json
|
|
178
|
+
{ "version": 1,
|
|
179
|
+
"owner": "release-manager",
|
|
180
|
+
"owns": ["org/release-manager"],
|
|
181
|
+
"reads": ["org/platform-engineer"] }
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
`owner` is the stable owner id (what `owners.json` pins, §7b); `owns` /
|
|
185
|
+
`reads` are `<base alias>/<node>` references into the bases the machine's
|
|
186
|
+
bindings file declares (`oats okf init` / `oats okf migrate` write it;
|
|
187
|
+
`capabilities/oats-okf/lib/config.mjs#validateDeclaration` is the
|
|
188
|
+
authority). Keep the file where 0.24 had it — it moves with the soul in
|
|
189
|
+
§3b. A soul without `okf.json` whose slot resolves to `oats.okf` fails the
|
|
190
|
+
required spawn hook (`soul has no okf.json`), by design.
|
|
191
|
+
2. **The merged payload, as `OATS_SETTINGS`** — the binding's **settings
|
|
192
|
+
keys only**, the list in `capabilities/oats-okf/oats.json#settings`:
|
|
193
|
+
`bindings-file`, `state-dir` (both required, absolute host paths →
|
|
194
|
+
`oats-local.yaml`, §5), `harvest-runtime`, `harvest-model` (optional). Any
|
|
195
|
+
other key — `owns`, `reads`, `store`, `root`, `stores` — is refused
|
|
196
|
+
(`unknown OATS_SETTINGS property`). So for `oats.okf` the soul's
|
|
197
|
+
`knowledge:` payload is normally **absent** (the workspace default
|
|
198
|
+
`defaults.knowledge: { oats.okf: { from: package } }` fills the slot) or
|
|
199
|
+
carries a soul-true binding setting such as `harvest-runtime: claude`;
|
|
200
|
+
`knowledge: none` opts the soul out.
|
|
201
|
+
|
|
202
|
+
`stores:` in the workspace file names repositories for the **workspace**; where
|
|
203
|
+
OKF's bases live inside them is a **bindings-file** concern today (`bases.<alias>`
|
|
204
|
+
with `repository` + `root`), not a soul payload key. A soul payload grammar for
|
|
205
|
+
OKF (`owns`/`reads`/`root` on `soul.yaml`) is an OKF follow-up (it lands with an
|
|
206
|
+
`oats.okf` release that declares it in its binding, and this guide will say so);
|
|
207
|
+
until then the kernel forwards the payload opaquely and OKF refuses what it does
|
|
208
|
+
not know.
|
|
209
|
+
|
|
167
210
|
**Carry `team:` on every soul, or on its repo's membership.** A soul's team is
|
|
168
211
|
`soul.yaml#team`, else `oats-membership.yaml#team`, else *unassigned*
|
|
169
212
|
(`null`). Labels never gate anything, but the kernel addresses provider payload
|
|
@@ -175,25 +218,27 @@ outside every team-addressed payload; nothing refuses it. Label the membership
|
|
|
175
218
|
when a whole repo belongs to one team, and the soul when it does not.
|
|
176
219
|
|
|
177
220
|
**Per-soul memory-harvest opt-out:** not available in OKF 2.1.3 — an OKF 2.1.4
|
|
178
|
-
item.
|
|
179
|
-
|
|
180
|
-
keeps a soul registered for reads while excluding it from harvest. A
|
|
181
|
-
must not be harvested today says `knowledge: none` (no OKF at all for
|
|
182
|
-
soul) or `oats.okf: off`; do not invent a key —
|
|
183
|
-
|
|
221
|
+
item. Neither `okf.json` (`version`, `owner`, `owns`, `reads`) nor the settings
|
|
222
|
+
payload (`bindings-file`, `state-dir`, `harvest-runtime`, `harvest-model`) has a
|
|
223
|
+
key that keeps a soul registered for reads while excluding it from harvest. A
|
|
224
|
+
soul that must not be harvested today says `knowledge: none` (no OKF at all for
|
|
225
|
+
that soul) or `oats.okf: off`; do not invent a key — both readers refuse unknown
|
|
226
|
+
keys.
|
|
184
227
|
|
|
185
228
|
## 5. Write `oats-local.yaml` on each machine
|
|
186
229
|
|
|
187
230
|
```
|
|
188
|
-
~/acme
|
|
231
|
+
~/acme/ # the directory YOU choose — an existing folder with your clones is the usual case
|
|
189
232
|
├── oats-local.yaml
|
|
190
|
-
├── agents/ # instance homes
|
|
191
|
-
└── platform/ # member clones,
|
|
233
|
+
├── agents/ # instance homes — created by `oats sync` if absent (0.25.2)
|
|
234
|
+
└── platform/ # member clones, wherever you keep them (here, or named in clones:)
|
|
192
235
|
```
|
|
193
236
|
|
|
194
237
|
```yaml
|
|
195
238
|
schemaVersion: 2
|
|
196
239
|
workspace: git:github.com/acme/agents
|
|
240
|
+
clones: # optional: member clones that are NOT at <deployment>/<member name>
|
|
241
|
+
github.com/acme/platform: /Users/ana/src/acme-platform
|
|
197
242
|
settings: # what used to be `settings:` under capabilities.layers.* in oats-config.yaml
|
|
198
243
|
oats.okf:
|
|
199
244
|
bindings-file: /Users/ana/.oats/okf-bindings.json # required by the OKF binding: absolute host path
|
|
@@ -205,6 +250,27 @@ souls:
|
|
|
205
250
|
disabled: [data-analyst]
|
|
206
251
|
```
|
|
207
252
|
|
|
253
|
+
**Where the kernel looks for a member clone** (a `work: worktree | checkout`
|
|
254
|
+
soul needs one; nothing else does). In this order, first hit wins:
|
|
255
|
+
|
|
256
|
+
1. `oats spawn … --repo <abs path>` — this spawn only.
|
|
257
|
+
2. `oats-local.yaml` `clones: { <repo key>: <abs path> }` — the key is the
|
|
258
|
+
member's **canonical key** (`github.com/acme/platform`; any ref spelling you
|
|
259
|
+
write is normalised through `parseRepoRef`, so `git:github.com/acme/platform`
|
|
260
|
+
and `https://github.com/acme/platform.git` address the same entry).
|
|
261
|
+
3. The convention: `<deployment>/<member name>` — the last path segment of the
|
|
262
|
+
repo key (`platform` for `github.com/acme/platform`). One exception: a member
|
|
263
|
+
whose name is `agents` is looked for at `<deployment>/agents-repo`, because
|
|
264
|
+
`<deployment>/agents/` is the instance root (above).
|
|
265
|
+
4. None found → `E_CLONE_MISSING`, naming the three remedies. A directory that
|
|
266
|
+
*is* found but whose `origin` remote is a **different repo** →
|
|
267
|
+
`E_CLONE_MISMATCH` (the clone is not the member; nothing is spawned into it).
|
|
268
|
+
|
|
269
|
+
This order was documented before 0.25.2 but the kernel did not honour it (a
|
|
270
|
+
clone had to be `--repo`'d or sit at the convention); 0.25.2 implements it as
|
|
271
|
+
written here. If your host repo is named `agents`, clone it as
|
|
272
|
+
`<deployment>/agents-repo` or name it in `clones:`.
|
|
273
|
+
|
|
208
274
|
`settings.<cap>` is merged into that capability's payload after the soul's
|
|
209
275
|
slot payload and before `spawn --provider` (decision 14); the keys are the
|
|
210
276
|
capability's own (`oats.json#settings`). For **`oats.okf` 2.1.3** the binding
|
|
@@ -220,8 +286,12 @@ here but see §8 for why it belongs at spawn.
|
|
|
220
286
|
Move host paths from `oats-config.yaml` `settings:` here; the `souls:` blocks of
|
|
221
287
|
`oats-config.yaml` become `--provider` flags at spawn (step 8). Delete
|
|
222
288
|
`oats-config.yaml`; it is not read. Do not commit `oats-local.yaml`.
|
|
223
|
-
(`oats onboard <dir> --workspace <repo ref>` writes a minimal `oats-local.yaml
|
|
224
|
-
and runs the first `sync` for you; add `settings:` afterwards.
|
|
289
|
+
(`oats onboard <dir> --workspace <repo ref>` writes a minimal `oats-local.yaml`,
|
|
290
|
+
creates `agents/` and runs the first `sync` for you; add `settings:` afterwards.
|
|
291
|
+
Its `next.clone` list names **every** member that lacks a clone at the
|
|
292
|
+
convention — the host included: the host is a member like any other, and a
|
|
293
|
+
soul that lives in it and says `work: worktree` needs its clone too. Under an
|
|
294
|
+
explicit `standalone:` header the list says so and names only that repo.)
|
|
225
295
|
|
|
226
296
|
## 6. `oats sync`
|
|
227
297
|
|
|
@@ -231,11 +301,17 @@ From the deployment directory:
|
|
|
231
301
|
oats sync
|
|
232
302
|
```
|
|
233
303
|
|
|
234
|
-
It
|
|
235
|
-
|
|
236
|
-
`
|
|
237
|
-
|
|
238
|
-
|
|
304
|
+
It creates `agents/` if it is absent (0.25.2; a hand-written `oats-local.yaml`
|
|
305
|
+
no longer needs a `mkdir`), confirms every member (fix any `no-backlink` /
|
|
306
|
+
`backlink-elsewhere` / `cannot-read` row before going on), resolves `packages:`
|
|
307
|
+
to commits, writes `oats-lock.json` (lockfileVersion 3) and asks for executable
|
|
308
|
+
approval once per package version. The 0.24 lock is not read; delete it
|
|
309
|
+
(`E_LOCK_SCHEMA` names it if you leave it in the way).
|
|
310
|
+
|
|
311
|
+
The legacy "You run on OATS" block is no longer composed into `AGENTS.md` when
|
|
312
|
+
`oats.core` resolves as a module (0.25.2): an instance gets **one** such block,
|
|
313
|
+
the one `oats.core`'s inject carries. If you see two, the soul resolved without
|
|
314
|
+
`oats.core` (check `oats spawn <soul> --preview`).
|
|
239
315
|
|
|
240
316
|
## 7. Approve packages
|
|
241
317
|
|
|
@@ -243,10 +319,23 @@ Approval is **per package version, once, in the lock** — no `oats trust`, no
|
|
|
243
319
|
per-capability approval, no per-operator trust list. `oats sync` on a terminal
|
|
244
320
|
prints every executable (`commands.*` and `hooks.*.command` targets of every
|
|
245
321
|
capability the package provides) and asks `approve <id> <version>? [y/N]`.
|
|
246
|
-
Declined or non-interactive → exit `2`, the lock
|
|
247
|
-
unapproved, and spawns of souls using it are refused
|
|
248
|
-
until you run `oats sync` in a terminal and say yes.
|
|
249
|
-
approval: membership is the trust.
|
|
322
|
+
Declined, **Ctrl+D at the prompt**, or non-interactive → exit `2`, the lock
|
|
323
|
+
records the entry unapproved, and spawns of souls using it are refused
|
|
324
|
+
(`E_PACKAGE_UNAPPROVED`) until you run `oats sync` in a terminal and say yes.
|
|
325
|
+
Member capabilities need no approval: membership is the trust.
|
|
326
|
+
|
|
327
|
+
**Non-interactive approval (CI, scripted rebuilds):**
|
|
328
|
+
|
|
329
|
+
```bash
|
|
330
|
+
oats sync --approve oats.okf@v2.1.3 --approve oats.aweb@v1.11.2
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
`--approve <id>@<version>` is repeatable and approves **exactly** the entry the
|
|
334
|
+
resolution contains for that id and version — the executables digest is always
|
|
335
|
+
computed by `sync` over the fetched tree and recorded in the lock; you never
|
|
336
|
+
type a digest. An `--approve` that names an id or version the resolution does
|
|
337
|
+
not contain is an error, not a silent skip; an entry the flags do not cover
|
|
338
|
+
stays unapproved (exit `2`, as above).
|
|
250
339
|
|
|
251
340
|
## 7b. OKF 2: start a FRESH `state-dir` — do not re-point the old one
|
|
252
341
|
|
|
@@ -309,16 +398,83 @@ machine-level setting would give the seat to EVERY instance of every messaging
|
|
|
309
398
|
soul on that machine, and a seat can be held once. The Desktop's
|
|
310
399
|
confirmed apply carries the same map.
|
|
311
400
|
|
|
401
|
+
## 8b. Where the team `.aw` lives now (oats.aweb 1.11.2), and what `byTeam` does today
|
|
402
|
+
|
|
403
|
+
A freshly minted identity (every spawn without `identity.source`) needs an
|
|
404
|
+
**initialised aweb root**: a directory holding `.aw` with a team membership to
|
|
405
|
+
mint into. oats.aweb 1.11.2's spawn hook looks for `.aw` among these, first hit
|
|
406
|
+
wins: the declared team scope (`OATS_TEAM_SCOPE`, from the removed
|
|
407
|
+
`oats-config.yaml` `team:` block — **empty under v2**), the instance home, the
|
|
408
|
+
git repo containing the home, the resolution context (the soul's work repo) and
|
|
409
|
+
the git repo containing it, and the workspace root (`OATS_WORKSPACE`, which
|
|
410
|
+
under v2 is the **deployment directory** — the one holding `oats-local.yaml`).
|
|
411
|
+
None of these is the 0.24 team root you initialised with `oats aweb setup`, so
|
|
412
|
+
a rebuilt deployment mints nothing until you put `.aw` where the hook looks:
|
|
413
|
+
|
|
414
|
+
- **at the deployment directory** — `<deployment>/.aw`: one team for every
|
|
415
|
+
messaging soul spawned here; or
|
|
416
|
+
- **inside a member clone** (gitignored — add `.aw/` to the clone's
|
|
417
|
+
`.gitignore`; never commit `signing.key`): `<clone>/.aw` is found through the
|
|
418
|
+
soul's work repo, so souls whose `work:` targets *that* member mint into
|
|
419
|
+
*that* team.
|
|
420
|
+
|
|
421
|
+
`cp -R <old team root>/.aw <deployment>/.aw` (or into the clone) carries the
|
|
422
|
+
existing memberships over; `aw team list` from that directory shows the active
|
|
423
|
+
team. A `.aw` at your user home or above the deployment is **not** found on
|
|
424
|
+
purpose (a `.aw` there would be a different team; minting into it would be a
|
|
425
|
+
silent cross-team leak).
|
|
426
|
+
|
|
427
|
+
**Two teams, two identities — what actually decides the team in 1.11.2.** The
|
|
428
|
+
hook resolves the target team as: `OATS_TEAM_ID` / `OATS_TEAM_NAME` from the
|
|
429
|
+
removed `oats-config.yaml` `team:` block (empty under v2), else **the active
|
|
430
|
+
team at the `.aw` root it found**. It **does not read a `team` key from its
|
|
431
|
+
payload** (`OATS_SETTINGS`): the only payload keys 1.11.2 acts on are
|
|
432
|
+
`delivery` and `identity.source`/`identity.takeOver`. Consequently
|
|
433
|
+
`messaging.byTeam.<label>: { team: aweb:… }` is **kernel-merged and
|
|
434
|
+
delivered, but a NO-OP for oats.aweb 1.11.2** — the kernel does its part
|
|
435
|
+
(`spawn --preview` shows the merged `settings.oats.aweb` with the label's
|
|
436
|
+
`team`, and `instance.json.providers.oats.aweb` records it); the provider
|
|
437
|
+
ignores it until an oats.aweb release reads `team` from the payload. Until then
|
|
438
|
+
the only way to get per-label minting is **per-repo placement**: give each
|
|
439
|
+
team's member clone its own `.aw` whose active team is that team’s, and make
|
|
440
|
+
sure the souls of that team say `work: worktree | checkout` **on that repo**.
|
|
441
|
+
A soul with `work: directory | workspace` has no member clone as context and
|
|
442
|
+
falls through to `<deployment>/.aw` — one team only. Keep `byTeam` in the
|
|
443
|
+
workspace file anyway: it is the declared intent, the kernel honours it, and
|
|
444
|
+
the next oats.aweb picks it up without a workspace edit.
|
|
445
|
+
|
|
312
446
|
## 9. Spawn, and check drift
|
|
313
447
|
|
|
314
448
|
```bash
|
|
315
449
|
oats souls # every non-private soul of every confirmed member, with origin and team
|
|
316
450
|
oats capabilities # every capability, member (origin: member <key> @ <commit>) or package (package <id> v<ver>)
|
|
317
|
-
oats spawn <soul> --preview # modules[] with from/commit/changedSince, team, resolution revision
|
|
451
|
+
oats spawn <soul> --preview # modules[] with from/commit/changedSince, team, resolution revision,
|
|
452
|
+
# providers (the --provider map as given) and settings.<cap> (the merged payload each provider receives)
|
|
318
453
|
oats spawn <soul> --purpose x
|
|
319
|
-
oats status # per instance:
|
|
454
|
+
oats status # per instance: soul: <name> from <member> @ <c7> [member moved since …]
|
|
455
|
+
# modules … [member moved since (now @ …)] / [capability no longer present]
|
|
320
456
|
```
|
|
321
457
|
|
|
458
|
+
`--preview` (0.25.2) prints `providers` — exactly the `--provider <cap> k=v`
|
|
459
|
+
map you gave — and `settings.<cap>` — the **merged** payload the provider's
|
|
460
|
+
binding will receive (`workspace.messaging` base ⊕ `byTeam[team]` ⊕ soul slot
|
|
461
|
+
payload ⊕ `oats-local.yaml settings.<cap>` ⊕ `--provider`), so you can see
|
|
462
|
+
before creating anything that `state-dir` is the fresh one (§7b) and that the
|
|
463
|
+
team block reached the payload (§8b). `oats status` (0.25.2) shows drift for
|
|
464
|
+
the **soul source** as well as for modules: `soul: <name> from <member> @ <c7>`
|
|
465
|
+
with `[member moved since …]` when the member's default branch has moved past
|
|
466
|
+
the commit the instance was spawned from; `--json` carries it as
|
|
467
|
+
`instances[].soul { repoKey, commit, current, status }`. A moved soul is
|
|
468
|
+
information, not a fault — the running instance keeps its own commit (§7b);
|
|
469
|
+
re-spawn when you want the new one.
|
|
470
|
+
|
|
471
|
+
**Work modes and clones.** `work: worktree | checkout` needs the member clone
|
|
472
|
+
(§5 order); `work: directory` needs nothing; `work: workspace` (a coordination
|
|
473
|
+
soul) links `./work` to the **deployment directory** — the one holding
|
|
474
|
+
`oats-local.yaml`, with `agents/` and whatever clones sit beside it — read-only
|
|
475
|
+
across members, no branch (0.25.1). Such a soul finds a member whose clone is
|
|
476
|
+
elsewhere through `oats-local.yaml` `clones:`.
|
|
477
|
+
|
|
322
478
|
## What disappears
|
|
323
479
|
|
|
324
480
|
| Gone | Replaced by |
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# OATS v0.25.2 — operator-rebuild round
|
|
2
|
+
|
|
3
|
+
Kernel/Pi **0.25.2**. Tag `v0.25.2` → the commit carrying these notes; the
|
|
4
|
+
version-bump commit lands after the tag. Consumers gate on `oats version --json`
|
|
5
|
+
`features[]` names and API integers — never on the version.
|
|
6
|
+
|
|
7
|
+
**No API change.** `workspaceApi: 2`, every other API integer and the
|
|
8
|
+
`features[]` list are exactly those of [0.25.1](v0.25.1.md). The only surface
|
|
9
|
+
additions are **additive fields**: `oats spawn --preview` gains `providers` and
|
|
10
|
+
`settings`, `oats status --json` gains `instances[].soul`; `oats sync` gains
|
|
11
|
+
the `--approve` flag. Every item below comes from an operator's first rebuild
|
|
12
|
+
of a real two-team deployment on 0.25.0, following `docs/rebuild-to-v2.md`
|
|
13
|
+
literally — the guide is a contract the kernel honours. Normative record:
|
|
14
|
+
"0.25.2 operator-rebuild round" in
|
|
15
|
+
`docs/design/2026-09-23-workspace-module-contracts.md` and the matching
|
|
16
|
+
clarifications in
|
|
17
|
+
`agents/oats-expert/soul/knowledge/decisions/workspace-model-v2.md`.
|
|
18
|
+
|
|
19
|
+
## Kernel
|
|
20
|
+
|
|
21
|
+
- **R1 — member clones are found as the guide says.** For a `work: worktree |
|
|
22
|
+
checkout` soul: `--repo`, then `oats-local.yaml` `clones:` (keys normalised
|
|
23
|
+
through `parseRepoRef`), then `<deployment>/<member name>` (a member named
|
|
24
|
+
`agents` → `agents-repo`); none → `E_CLONE_MISSING` naming the three
|
|
25
|
+
remedies; a directory whose remotes name another repo → `E_CLONE_MISMATCH`.
|
|
26
|
+
- **R2 — `oats sync` creates `<deployment>/agents/` when absent.**
|
|
27
|
+
- **R3 — one "You run on OATS" block.** With `oats.core` resolved as a module
|
|
28
|
+
the kernel's legacy block is suppressed; the module's inject is the briefing.
|
|
29
|
+
- **R4 — `oats status` shows soul-source drift.** `soul: <name> from <member>
|
|
30
|
+
@ <c7> [member moved since …]` beside the module rows; `--json`
|
|
31
|
+
`instances[].soul { repoKey, commit, current, status }`.
|
|
32
|
+
- **R5 — `spawn --preview` shows the payload.** `providers` (the `--provider`
|
|
33
|
+
map as given) and `settings.<cap>` (the merged payload each provider
|
|
34
|
+
receives).
|
|
35
|
+
- **R9 — non-interactive approval.** `oats sync --approve <id>@<version>`
|
|
36
|
+
(repeatable) approves exactly the locked entry at that version; the digest is
|
|
37
|
+
always computed, never typed; an id/version not in the resolution is
|
|
38
|
+
`E_BAD_ARGS`. Ctrl+D at the interactive prompt is a decline (exit `2`).
|
|
39
|
+
- **R10 — `oats onboard` lists the host** among the clones to make, like any
|
|
40
|
+
member; an explicit `standalone:` header is named as such in the next steps.
|
|
41
|
+
|
|
42
|
+
## Documentation
|
|
43
|
+
|
|
44
|
+
- **R6** — the rebuild guide's work-mode section states that a coordination
|
|
45
|
+
soul's (`work: workspace`) `./work` is the deployment directory (0.25.1 B2).
|
|
46
|
+
- **R7 — where the team `.aw` lives now.** Rebuild guide §8b: oats.aweb 1.11.2
|
|
47
|
+
finds `.aw` among the home, the home's git repo, the soul's work repo and the
|
|
48
|
+
deployment directory — never the 0.24 team root; place it inside the member
|
|
49
|
+
clone (gitignored) or at the deployment directory. **`messaging.byTeam` is
|
|
50
|
+
kernel-merged but a no-op for oats.aweb 1.11.2**, which does not read `team`
|
|
51
|
+
from its payload; per-repo `.aw` placement is the working alternative
|
|
52
|
+
(`workspaces.md` byTeam paragraph updated accordingly).
|
|
53
|
+
- **R8 — OKF 2.1.3 reads `okf.json`, not a soul payload.** The guide, `workspaces.md`,
|
|
54
|
+
`souls-and-instances.md` and `knowledge.md` no longer show `knowledge: { owns,
|
|
55
|
+
reads }` / `{ store, root }` on `soul.yaml`: the soul's declaration is
|
|
56
|
+
`souls/<name>/okf.json` (`version`, `owner`, `owns`, `reads`); the `knowledge:`
|
|
57
|
+
payload may carry only the binding's settings (`bindings-file`, `state-dir`,
|
|
58
|
+
`harvest-runtime`, `harvest-model`); a base's `root` is the bindings file's.
|
|
59
|
+
Decision 24's example is corrected. §7b (fresh `state-dir`) confirmed.
|
|
60
|
+
- `configuration.md` `clones` row states the R1 order; `workspaces.md` and
|
|
61
|
+
`souls-and-instances.md` describe the R4/R5 fields and the per-commit soul
|
|
62
|
+
cache paths.
|
|
63
|
+
|
|
64
|
+
## Provider follow-ups (not kernel)
|
|
65
|
+
|
|
66
|
+
- **oats.aweb follow-up:** read `team` from the spawn payload (so
|
|
67
|
+
`messaging.byTeam` yields per-label identities) and accept the deployment
|
|
68
|
+
directory as a first-class aweb root. Until that release, 1.11.2 behaves as
|
|
69
|
+
§8b describes.
|
|
70
|
+
- **oats.okf follow-up:** a soul-payload grammar (`owns`/`reads` on
|
|
71
|
+
`soul.yaml`), declared in the binding when it lands; owner pin re-based on
|
|
72
|
+
identity rather than path, and per-soul harvest opt-out (2.1.4 items, unchanged).
|
|
73
|
+
|
|
74
|
+
## Known follow-ups (unchanged from 0.25.1)
|
|
75
|
+
|
|
76
|
+
- The classic (no `oats-local.yaml`) spawn path still runs the pre-v2 compose.
|
|
77
|
+
- The readiness quartet (`readinessApi: 1`) is still produced by the 0.24 tier
|
|
78
|
+
observers.
|
|
79
|
+
- `oats-local.yaml` `transport:` needs a schema addition before it can be
|
|
80
|
+
written.
|
|
@@ -25,10 +25,12 @@ A soul is durable and committed. It is the part you review, improve, and keep.
|
|
|
25
25
|
AGENTS.md # canonical operating doc
|
|
26
26
|
CLAUDE.md → AGENTS.md
|
|
27
27
|
skills/ # skills specific to this expert
|
|
28
|
+
okf.json # if the soul uses oats.okf: { version: 1, owner, owns: ["<base>/<node>"], reads: […] } — the provider's, not the kernel's
|
|
28
29
|
```
|
|
29
30
|
|
|
30
|
-
(A deployment's
|
|
31
|
-
fetched there at its discovered commit before its first spawn
|
|
31
|
+
(A deployment's `agents/<name>/souls/<commit12>/` has the same shape; a member
|
|
32
|
+
soul is fetched there at its discovered commit before its first spawn, and
|
|
33
|
+
`agents/<name>/soul` points at the current commit.)
|
|
32
34
|
|
|
33
35
|
### `soul.yaml` v2
|
|
34
36
|
|
|
@@ -47,8 +49,8 @@ capabilities: # WHERE each capability comes from —
|
|
|
47
49
|
acme-house-style: off # removes a workspace/team default
|
|
48
50
|
|
|
49
51
|
knowledge: # provider payloads — opaque to the kernel, consumed by the slot's capability
|
|
50
|
-
|
|
51
|
-
|
|
52
|
+
harvest-runtime: claude # (oats.okf 2.1.3 reads only its binding's settings keys here; what the soul
|
|
53
|
+
# owns/reads is in this directory's okf.json — see "Soul anatomy")
|
|
52
54
|
messaging:
|
|
53
55
|
channels: [acme-eng]
|
|
54
56
|
tasks: none # `none` empties the slot (drops the workspace default)
|
|
@@ -63,7 +65,7 @@ compatibility: # optional floors on PACKAGE versions
|
|
|
63
65
|
| `team` | A label declared in the workspace's `teams:`; may add `defaults.byTeam` capabilities. Never gates or restricts. |
|
|
64
66
|
| `private` | `true` keeps the soul out of workspace discovery; its own repo can still spawn it. |
|
|
65
67
|
| `capabilities` | `<cap>: { from: here \| <repo key> \| package }` or `<cap>: off`. Composed over `defaults.<slot>` ⊕ `defaults.capabilities` ⊕ `defaults.byTeam[team]`; the soul wins. |
|
|
66
|
-
| `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. |
|
|
68
|
+
| `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. |
|
|
67
69
|
| `compatibility` | `<cap>: <semver range>` checked against the locked package version (`E_COMPATIBILITY`). |
|
|
68
70
|
|
|
69
71
|
Schema: [`soul.schema.json`](soul.schema.json). Not in v2: `kind`, `type`,
|
|
@@ -82,7 +84,9 @@ souls — is ordinary capability content: **`oats.core`** (package
|
|
|
82
84
|
`defaults.capabilities: { oats.core: { from: package } }`; a soul may say
|
|
83
85
|
`oats.core: off`. **`oats.setup`** (same package) carries the whole-architecture
|
|
84
86
|
knowledge an onboarding expert needs. Neither is kernel magic; the kernel still
|
|
85
|
-
composes its own instance-boundary and work-mode briefings
|
|
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).
|
|
86
90
|
|
|
87
91
|
## Instance anatomy
|
|
88
92
|
|
|
@@ -134,7 +138,7 @@ skills and instructions), a workspace spawn records:
|
|
|
134
138
|
}
|
|
135
139
|
},
|
|
136
140
|
"providers": {
|
|
137
|
-
"oats.okf": { "
|
|
141
|
+
"oats.okf": { "bindings-file": "/Users/ana/.oats/okf-bindings.json", "state-dir": "/Users/ana/.oats/okf", "harvest-runtime": "claude" },
|
|
138
142
|
"acme-release-tooling": {}
|
|
139
143
|
},
|
|
140
144
|
"workspace": {
|
|
@@ -149,9 +153,13 @@ skills and instructions), a workspace spawn records:
|
|
|
149
153
|
shows `moved` / `missing` per module (drift is shown, not prevented).
|
|
150
154
|
- `providers.<cap>` — the merged provider payload the capability was bound with
|
|
151
155
|
(soul ⊕ machine settings ⊕ `--provider`), so a later inspection can tell
|
|
152
|
-
which instance holds a retained seat or a one-off state root.
|
|
156
|
+
which instance holds a retained seat or a one-off state root. `spawn
|
|
157
|
+
--preview` shows the same map before anything exists, as `settings.<cap>`,
|
|
158
|
+
beside `providers` (the `--provider` flags as given).
|
|
153
159
|
- `workspace` — the workspace commit observed at spawn, the soul's repo/commit/
|
|
154
|
-
team, and the **resolution revision** the spawn decision bound.
|
|
160
|
+
team, and the **resolution revision** the spawn decision bound. `oats status`
|
|
161
|
+
compares `workspace.soul` with the member's current commit too: `soul: <name>
|
|
162
|
+
from <member> @ <c7> [member moved since …]` (`--json`: `instances[].soul`).
|
|
155
163
|
|
|
156
164
|
A running instance never changes under itself: a member moving or a package
|
|
157
165
|
bump affects only new spawns.
|
|
@@ -183,7 +191,9 @@ From a deployment (where `oats-local.yaml` is), a spawn: reads the local file
|
|
|
183
191
|
discovers the workspace over its remotes and confirms membership → finds the
|
|
184
192
|
soul among the confirmed members (or `external:`; an ambiguous bare name is
|
|
185
193
|
`E_SOUL_AMBIGUOUS` — say `<repo>/<soul>`) → fetches the soul's source into
|
|
186
|
-
`<agents-root>/<soul>/
|
|
194
|
+
`<agents-root>/<soul>/souls/<commit12>/` at its commit (the home links that
|
|
195
|
+
directory; `<agents-root>/<soul>/soul` points at the current one) → resolves
|
|
196
|
+
every capability by
|
|
187
197
|
`from:` (member = latest, package = locked + approved) → creates the home →
|
|
188
198
|
**materializes each module whole** into `.oats/modules/` and copies its skills
|
|
189
199
|
into `.agents/skills/` (a transaction: any failure leaves nothing behind) →
|
|
@@ -194,8 +204,10 @@ unchanged. This is a normal agent process with its own home and tools, not a
|
|
|
194
204
|
subagent call.
|
|
195
205
|
|
|
196
206
|
`--preview` reports `modules[]` (`from`, `layer`, `changedSince` the newest
|
|
197
|
-
previous instance of the soul), `team`, the `resolution` revision
|
|
198
|
-
|
|
207
|
+
previous instance of the soul), `team`, the `resolution` revision, the decision
|
|
208
|
+
it would bind, `providers` (the `--provider` map exactly as given) and
|
|
209
|
+
`settings.<cap>` (the merged payload each provider's binding will receive);
|
|
210
|
+
the apply refuses with `E_DECISION_STALE` if a member
|
|
199
211
|
moved in between. `--provider <cap> key=value` (repeatable; dotted keys nest)
|
|
200
212
|
must name a capability the soul resolves (`E_CAPABILITY_MISSING` otherwise) and
|
|
201
213
|
needs a workspace deployment. The full DTOs are in
|
|
@@ -324,7 +336,10 @@ directory is for, not a place to settle in.
|
|
|
324
336
|
### `worktree` — isolated branch
|
|
325
337
|
|
|
326
338
|
`work/` is a git worktree on the instance's own branch, by default
|
|
327
|
-
`agents/<instance>`.
|
|
339
|
+
`agents/<instance>`. The worktree is created from the member's **clone**, found
|
|
340
|
+
as `--repo`, then `oats-local.yaml` `clones:`, then `<deployment>/<member name>`
|
|
341
|
+
(`E_CLONE_MISSING` / `E_CLONE_MISMATCH` otherwise — see
|
|
342
|
+
[configuration.md](configuration.md)).
|
|
328
343
|
|
|
329
344
|
Use this for agents that will edit code or docs independently.
|
|
330
345
|
|
|
@@ -341,7 +356,8 @@ inside each fresh worktree. Failures warn but do not block spawn.
|
|
|
341
356
|
|
|
342
357
|
### `checkout` — shared current branch
|
|
343
358
|
|
|
344
|
-
`work/` is a symlink to the repo checkout itself
|
|
359
|
+
`work/` is a symlink to the repo checkout itself (the member clone, found as for
|
|
360
|
+
`worktree`).
|
|
345
361
|
|
|
346
362
|
Use this for maintainers, coordinators, auditors, or agents working on the
|
|
347
363
|
repo's current state.
|
|
@@ -382,8 +398,10 @@ exchanged for a symlink. Recovery does not replace the worker's delivery protoco
|
|
|
382
398
|
|
|
383
399
|
### `workspace` — cross-repo coordinator
|
|
384
400
|
|
|
385
|
-
`work/` is a symlink to the **whole deployment**
|
|
386
|
-
|
|
401
|
+
`work/` is a symlink to the **whole deployment** — the directory holding
|
|
402
|
+
`oats-local.yaml`, with `agents/` and the member clones that sit beside it —
|
|
403
|
+
not a repo (0.25.1; a member cloned elsewhere is reached through
|
|
404
|
+
`oats-local.yaml` `clones:`). Every
|
|
387
405
|
member repo is read-context; the instance's product is coordination:
|
|
388
406
|
routing, analysis, task-writing, messaging, spawning specialists.
|
|
389
407
|
|