@awebai/oats 0.24.13 → 0.25.1
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 +994 -2837
- package/docs/capabilities.md +136 -323
- package/docs/configuration.md +68 -533
- package/docs/conventions.md +51 -24
- package/docs/design/2026-09-16-fresh-operator-walkthrough.md +19 -14
- package/docs/design/2026-09-16-portable-migration-evidence.md +2 -0
- package/docs/design/2026-09-16-portable-onboarding.md +4 -2
- package/docs/design/2026-09-20-redesign-program-board.md +1 -1
- package/docs/design/2026-09-20-workspace-and-portable-adoption-plan.md +2 -0
- package/docs/design/2026-09-23-simplified-workspace-model.md +711 -0
- package/docs/design/2026-09-23-workspace-module-contracts.md +460 -0
- package/docs/design/2026-09-23-workspace-v2-implementation-plan.md +65 -0
- package/docs/design/README.md +20 -8
- package/docs/design/operations-contract.md +1 -0
- package/docs/design/package-engine-contract.md +1 -1
- package/docs/design/package-runtime-api.md +1 -1
- package/docs/desktop-cli-api.md +356 -5
- package/docs/desktop-succession.md +12 -6
- package/docs/desktop.md +9 -4
- package/docs/execution-targets.md +16 -4
- package/docs/first-team.md +107 -224
- package/docs/implementation.md +41 -11
- package/docs/integrations.md +50 -47
- package/docs/knowledge-capability-authoring.md +10 -4
- package/docs/knowledge-migration.md +21 -12
- package/docs/knowledge-reference/package-craft.md +11 -3
- package/docs/knowledge.md +60 -18
- package/docs/layers.md +3 -3
- package/docs/migration-from-oas.md +20 -9
- package/docs/oats-local.schema.json +50 -0
- package/docs/oats-membership.schema.json +23 -0
- package/docs/oats-workspace.schema.json +133 -48
- package/docs/official-marketplace.md +9 -6
- package/docs/packages.md +229 -440
- package/docs/rebuild-to-v2.md +347 -0
- package/docs/release-notes/v0.25.0.md +99 -0
- package/docs/release-notes/v0.25.1.md +94 -0
- package/docs/schedules.md +12 -6
- package/docs/soul.schema.json +41 -68
- package/docs/souls-and-instances.md +175 -108
- package/docs/workspace-adoption.md +70 -345
- package/docs/workspaces.md +436 -119
- package/lib/core.mjs +462 -61
- package/lib/instance-resolution.mjs +387 -0
- package/lib/materialize.mjs +580 -0
- package/lib/operator-dispatch.mjs +117 -0
- package/lib/packages.mjs +558 -1269
- package/lib/remote.mjs +718 -0
- package/lib/resolve.mjs +638 -0
- package/lib/schedule.mjs +90 -16
- package/lib/workspace.mjs +654 -0
- package/package.json +1 -1
- package/lib/portable-migration-artifacts.mjs +0 -135
- package/lib/portable-migration-evidence.mjs +0 -305
- package/lib/portable-migration-store.mjs +0 -199
- package/lib/portable-migration.mjs +0 -104
- package/lib/portable-onboarding-acceptance.mjs +0 -66
- package/lib/setup-expert-source.mjs +0 -100
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
# Rebuilding a 0.24.x deployment for the workspace model (0.25)
|
|
2
|
+
|
|
3
|
+
The workspace model ([workspaces.md](workspaces.md)) is a **clean v2**: no
|
|
4
|
+
converter, no dual-schema reader, no `oats migrate`. This guide is what ships
|
|
5
|
+
instead (decision 15 of `workspace-model-v2`). It is short because the new
|
|
6
|
+
surface is small: three shared files, one local file, one command.
|
|
7
|
+
|
|
8
|
+
## 0. 0.24.x keeps working
|
|
9
|
+
|
|
10
|
+
A 0.24.x kernel keeps spawning 0.24.x deployments indefinitely. Nothing forces
|
|
11
|
+
the move: install 0.25 when you are ready to rebuild, not before. A 0.25 kernel
|
|
12
|
+
reads only v2 files — a 0.24 `oats-workspace.yaml` (`schemaVersion: 1`), a
|
|
13
|
+
`soul.yaml` with `requires:`/`source:`, an `oats.yaml`, an `oats-config.yaml` or
|
|
14
|
+
a lock v1/v2 is an error **naming the schema** (`E_WORKSPACE_SCHEMA "… reads
|
|
15
|
+
schemaVersion 2 only; found 1"`, `E_LOCK_SCHEMA`), never a silent fallback.
|
|
16
|
+
Keep the 0.24 kernel installed until the last 0.24 deployment you care about is
|
|
17
|
+
rebuilt; the two do not share files.
|
|
18
|
+
|
|
19
|
+
**One thing a 0.25 kernel changes for a classic home it does launch.** Decision
|
|
20
|
+
13 ("harnesses start normally") is a property of the 0.25 *launcher*, not of the
|
|
21
|
+
v2 files: every `pi` launch a 0.25 kernel performs — `oats spawn`, `oats session
|
|
22
|
+
start|restart`, scheduled runs — starts pi with cwd = the instance home and pi's
|
|
23
|
+
own skill and context discovery intact (`--append-system-prompt <home>/AGENTS.md`,
|
|
24
|
+
no `--no-skills` / `--no-context-files` / `--no-prompt-templates` exclusion).
|
|
25
|
+
That holds for a classic 0.24 home (no `oats-local.yaml`, spawned through the
|
|
26
|
+
pre-v2 compose path that 0.25 still carries) exactly as for a module home. If you
|
|
27
|
+
relied on 0.24's ambient-skill exclusion to hide machine-level or repo-level
|
|
28
|
+
skills from an instance, that isolation is gone the moment a 0.25 kernel
|
|
29
|
+
launches it — keep the 0.24 kernel for those homes, or accept the ambient set
|
|
30
|
+
(the spawn preview lists composed skill names so a clash is visible).
|
|
31
|
+
|
|
32
|
+
## 1. Decide the one workspace
|
|
33
|
+
|
|
34
|
+
One workspace per organisation. Pick the repo that **hosts**
|
|
35
|
+
`oats-workspace.yaml` (a dedicated `agents` repo is common; any member can host
|
|
36
|
+
it). Decide the team labels you want (`global`, `engineering`, …) — labels
|
|
37
|
+
organise and may add defaults; they never gate anything.
|
|
38
|
+
|
|
39
|
+
**If any member is private, host the workspace file in a private repo that is
|
|
40
|
+
not a public member.** The workspace file names every member, so whoever can
|
|
41
|
+
read it sees the member list: a public host would publish the private repo's
|
|
42
|
+
name; hosting inside the private member hides the workspace from public
|
|
43
|
+
contributors entirely. A dedicated private repo (`<org>/workspace`) is the
|
|
44
|
+
honest shape. Public contributors who can read a public member but not the
|
|
45
|
+
host still get that member's souls through the standalone case (`from: here`
|
|
46
|
+
capabilities plus `oats.core`), so a public soul stays usable.
|
|
47
|
+
|
|
48
|
+
Two teams that need two different messaging identities (an open-source team
|
|
49
|
+
and a hosted-operations team, say) stay in ONE workspace: `team:` is a label,
|
|
50
|
+
and the provider payload is addressed by label under `messaging.byTeam` (§2).
|
|
51
|
+
|
|
52
|
+
## 2. Write `oats-workspace.yaml` v2 in the host repo
|
|
53
|
+
|
|
54
|
+
Start from the 0.24 file and rewrite it:
|
|
55
|
+
|
|
56
|
+
| 0.24 | v2 |
|
|
57
|
+
|---|---|
|
|
58
|
+
| `schemaVersion: 1` | `schemaVersion: 2` |
|
|
59
|
+
| `members: [{ source: git:… }]` | `members: [git:…]` — plain refs, **no** `@revision` |
|
|
60
|
+
| `imports:` of your **own** repos' souls | delete — member souls are discovered by convention |
|
|
61
|
+
| `imports:` of a **stranger's** soul (with `revision`) | `external: [{ source: git:<repo>@<full OID>, soul: <path> }]` |
|
|
62
|
+
| `teams: { private: per-human }` (the messaging payload) | `messaging: { private: per-human }`; `teams:` now declares **labels** |
|
|
63
|
+
| `defaults.knowledge: { capability, source }` | `defaults.knowledge: { <cap>: { from: package } }` (one entry, or `none`) |
|
|
64
|
+
| per-soul `stores.<x>.inherit` | `stores: { <name>: git:<repo> }` once, here |
|
|
65
|
+
| `catalog:` | delete (bare versions use the official catalog; `OATS_PACKAGE_CATALOG` overrides) |
|
|
66
|
+
| — | `packages: { <id>: <version> \| git:<repo>@<ref> }` — every version your souls used to carry in `source:` lines, **once** |
|
|
67
|
+
| — | `defaults.capabilities: { oats.core: { from: package } }` and whatever every soul should get |
|
|
68
|
+
|
|
69
|
+
```yaml
|
|
70
|
+
schemaVersion: 2
|
|
71
|
+
name: acme
|
|
72
|
+
members:
|
|
73
|
+
- git:github.com/acme/agents
|
|
74
|
+
- git:github.com/acme/platform
|
|
75
|
+
packages:
|
|
76
|
+
oats.framework: v1.1.3
|
|
77
|
+
oats.okf: v2.1.3
|
|
78
|
+
oats.aweb: v1.11.2
|
|
79
|
+
teams:
|
|
80
|
+
global: { description: Org-wide }
|
|
81
|
+
engineering: { description: Platform }
|
|
82
|
+
defaults:
|
|
83
|
+
capabilities: { oats.core: { from: package } }
|
|
84
|
+
knowledge: { oats.okf: { from: package } }
|
|
85
|
+
messaging: { oats.aweb: { from: package } }
|
|
86
|
+
tasks: none
|
|
87
|
+
stores:
|
|
88
|
+
org: git:github.com/acme/knowledge
|
|
89
|
+
messaging:
|
|
90
|
+
private: per-human
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
No absolute paths anywhere (they belong in `oats-local.yaml`). `from:` values
|
|
94
|
+
that name a repo are **canonical keys** — `github.com/acme/agents`, not
|
|
95
|
+
`git:github.com/acme/agents` and not `https://…`.
|
|
96
|
+
|
|
97
|
+
## 3. Add `oats-membership.yaml` to every member (replaces `oats.yaml`)
|
|
98
|
+
|
|
99
|
+
```yaml
|
|
100
|
+
schemaVersion: 2
|
|
101
|
+
workspace: git:github.com/acme/agents
|
|
102
|
+
team: engineering # optional default label for this repo's souls/capabilities
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Delete `oats.yaml`. Its `exports:` lists are gone: every `souls/*/soul.yaml` and
|
|
106
|
+
`capabilities/*/oats.json` is discoverable; add `private: true` to the ones that
|
|
107
|
+
should stay internal. The host repo backlinks to itself like any member.
|
|
108
|
+
|
|
109
|
+
## 3b. Move the souls: `agents/<name>/soul/` → `souls/<name>/`
|
|
110
|
+
|
|
111
|
+
In 0.24 a repo's souls lived at `agents/<name>/soul/` beside that soul's
|
|
112
|
+
instances. Under v2 discovery looks **only** at `souls/<name>/soul.yaml`; the
|
|
113
|
+
`agents/` directory belongs to the *deployment* (instance homes and, under the
|
|
114
|
+
kernel's per-commit soul cache, the fetched soul copies — see §7b) and is not
|
|
115
|
+
read as a soul source. Move every soul as a tracked rename so history follows:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
mkdir -p souls
|
|
119
|
+
git mv agents/release-manager/soul souls/release-manager
|
|
120
|
+
# … one line per soul; then
|
|
121
|
+
git rm -r --cached agents 2>/dev/null; echo 'agents/' >> .gitignore # instances were never meant to be tracked
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
`souls/<name>/` keeps its `AGENTS.md`, `CLAUDE.md → AGENTS.md` alias, `skills/`,
|
|
125
|
+
`knowledge/` and `soul.yaml` (rewritten in §4); the directory name must equal
|
|
126
|
+
`soul.yaml#name`. Then fix whatever enumerates the old path: repo tests, scripts,
|
|
127
|
+
CI checks and any `oats.yaml`-era `exports:` tooling that globbed
|
|
128
|
+
`agents/*/soul/soul.yaml` (`git grep -n 'agents/.*/soul'` finds them) — under v2
|
|
129
|
+
they enumerate `souls/*/soul.yaml`. A soul left under `agents/` is invisible to
|
|
130
|
+
`oats souls` and to `oats spawn`; nothing warns about it.
|
|
131
|
+
|
|
132
|
+
## 4. Edit every `soul.yaml` to v2
|
|
133
|
+
|
|
134
|
+
| 0.24 | v2 |
|
|
135
|
+
|---|---|
|
|
136
|
+
| `schemaVersion: 1` | `schemaVersion: 2` |
|
|
137
|
+
| `requires.knowledge: { capability: oats.okf, source: git:…@v2.1.3#oats-package }` | `capabilities: { oats.okf: { from: package } }` — or nothing, if the workspace default already says so |
|
|
138
|
+
| `requires.capabilities.<cap>: { source: git:… }` | `<cap>: { from: package }` (published) or `<cap>: { from: here }` / `{ from: <repo key> }` (a member capability) |
|
|
139
|
+
| `source: repo:…` / `path:` | `{ from: here }` |
|
|
140
|
+
| `defaults.capabilities` | fold into `capabilities:`; use `off` to remove a workspace default |
|
|
141
|
+
| `stores.inherit` | delete (stores are declared once in the workspace) |
|
|
142
|
+
| `imports` | delete |
|
|
143
|
+
| `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 |
|
|
145
|
+
| — | `compatibility: { <cap>: ">=x.y" }` if you want a floor |
|
|
146
|
+
|
|
147
|
+
```yaml
|
|
148
|
+
schemaVersion: 2
|
|
149
|
+
name: release-manager
|
|
150
|
+
description: Cuts, verifies and announces releases.
|
|
151
|
+
work: worktree
|
|
152
|
+
team: engineering
|
|
153
|
+
capabilities:
|
|
154
|
+
acme-release-tooling: { from: here }
|
|
155
|
+
knowledge:
|
|
156
|
+
owns: release-manager
|
|
157
|
+
reads: [platform-engineer]
|
|
158
|
+
messaging:
|
|
159
|
+
channels: [acme-eng]
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
`name` must equal the soul's directory name; `name`, `description` and `work`
|
|
163
|
+
are required. Capabilities the repo exports live at
|
|
164
|
+
`capabilities/<name>/oats.json` — the manifest is unchanged; you may add
|
|
165
|
+
`private: true` / `team:`.
|
|
166
|
+
|
|
167
|
+
**Carry `team:` on every soul, or on its repo's membership.** A soul's team is
|
|
168
|
+
`soul.yaml#team`, else `oats-membership.yaml#team`, else *unassigned*
|
|
169
|
+
(`null`). Labels never gate anything, but the kernel addresses provider payload
|
|
170
|
+
by label: an unlabelled soul receives the messaging **base** payload only —
|
|
171
|
+
`workspace.messaging` minus `byTeam`, no `byTeam.<label>` block, and no
|
|
172
|
+
`defaults.byTeam.<label>` capabilities either. If your 0.24 deployment had one
|
|
173
|
+
messaging identity per team (§1), a soul that loses its label silently lands
|
|
174
|
+
outside every team-addressed payload; nothing refuses it. Label the membership
|
|
175
|
+
when a whole repo belongs to one team, and the soul when it does not.
|
|
176
|
+
|
|
177
|
+
**Per-soul memory-harvest opt-out:** not available in OKF 2.1.3 — an OKF 2.1.4
|
|
178
|
+
item. The 2.1.3 `knowledge:` payload admits `owner`, `owns`, `reads` and
|
|
179
|
+
`stores` (plus the kernel-rendered `runtime`/`execution`); there is no key that
|
|
180
|
+
keeps a soul registered for reads while excluding it from harvest. A soul that
|
|
181
|
+
must not be harvested today says `knowledge: none` (no OKF at all for that
|
|
182
|
+
soul) or `oats.okf: off`; do not invent a key — the binding refuses unknown
|
|
183
|
+
payload keys.
|
|
184
|
+
|
|
185
|
+
## 5. Write `oats-local.yaml` on each machine
|
|
186
|
+
|
|
187
|
+
```
|
|
188
|
+
~/acme-workspace/ # the taught convention: "<name>-workspace"
|
|
189
|
+
├── oats-local.yaml
|
|
190
|
+
├── agents/ # instance homes
|
|
191
|
+
└── platform/ # member clones, only where someone works IN them
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
```yaml
|
|
195
|
+
schemaVersion: 2
|
|
196
|
+
workspace: git:github.com/acme/agents
|
|
197
|
+
settings: # what used to be `settings:` under capabilities.layers.* in oats-config.yaml
|
|
198
|
+
oats.okf:
|
|
199
|
+
bindings-file: /Users/ana/.oats/okf-bindings.json # required by the OKF binding: absolute host path
|
|
200
|
+
state-dir: /Users/ana/.oats/okf-state # required by the OKF binding: absolute host path; FRESH for a rebuilt deployment (§7b)
|
|
201
|
+
harvest-runtime: pi # optional: pi | claude | codex (default pi)
|
|
202
|
+
oats.aweb:
|
|
203
|
+
delivery: channel # channel (default) | session — see capabilities/oats-aweb/oats.json#settings.delivery
|
|
204
|
+
souls:
|
|
205
|
+
disabled: [data-analyst]
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
`settings.<cap>` is merged into that capability's payload after the soul's
|
|
209
|
+
slot payload and before `spawn --provider` (decision 14); the keys are the
|
|
210
|
+
capability's own (`oats.json#settings`). For **`oats.okf` 2.1.3** the binding
|
|
211
|
+
requires both `bindings-file` and `state-dir` as normalized absolute host
|
|
212
|
+
paths (`setting state-dir is required (absolute host path)` is a refusal, not a
|
|
213
|
+
default) and accepts `harvest-runtime` / `harvest-model`. For **`oats.aweb`**
|
|
214
|
+
the one machine-level key is `delivery`: `channel` (the native aweb channel
|
|
215
|
+
packages wake the instance; default) or `session` (delivery is external —
|
|
216
|
+
`AWEB_DELIVERY=session`, the host wake broker registers the instance once it
|
|
217
|
+
exists; requires an `aw` that ships `aw wake`). `identity.source` is also legal
|
|
218
|
+
here but see §8 for why it belongs at spawn.
|
|
219
|
+
|
|
220
|
+
Move host paths from `oats-config.yaml` `settings:` here; the `souls:` blocks of
|
|
221
|
+
`oats-config.yaml` become `--provider` flags at spawn (step 8). Delete
|
|
222
|
+
`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.)
|
|
225
|
+
|
|
226
|
+
## 6. `oats sync`
|
|
227
|
+
|
|
228
|
+
From the deployment directory:
|
|
229
|
+
|
|
230
|
+
```
|
|
231
|
+
oats sync
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
It confirms every member (fix any `no-backlink` / `backlink-elsewhere` /
|
|
235
|
+
`cannot-read` row before going on), resolves `packages:` to commits, writes
|
|
236
|
+
`oats-lock.json` (lockfileVersion 3) and asks for executable approval once per
|
|
237
|
+
package version. The 0.24 lock is not read; delete it (`E_LOCK_SCHEMA` names
|
|
238
|
+
it if you leave it in the way).
|
|
239
|
+
|
|
240
|
+
## 7. Approve packages
|
|
241
|
+
|
|
242
|
+
Approval is **per package version, once, in the lock** — no `oats trust`, no
|
|
243
|
+
per-capability approval, no per-operator trust list. `oats sync` on a terminal
|
|
244
|
+
prints every executable (`commands.*` and `hooks.*.command` targets of every
|
|
245
|
+
capability the package provides) and asks `approve <id> <version>? [y/N]`.
|
|
246
|
+
Declined or non-interactive → exit `2`, the lock records the entry
|
|
247
|
+
unapproved, and spawns of souls using it are refused (`E_PACKAGE_UNAPPROVED`)
|
|
248
|
+
until you run `oats sync` in a terminal and say yes. Member capabilities need no
|
|
249
|
+
approval: membership is the trust.
|
|
250
|
+
|
|
251
|
+
## 7b. OKF 2: start a FRESH `state-dir` — do not re-point the old one
|
|
252
|
+
|
|
253
|
+
OKF 2 pins each knowledge **owner** to a soul by path: at source registration
|
|
254
|
+
(the `oats.okf` spawn hook) it writes `owners.json` in `state-dir` as
|
|
255
|
+
`{ <owner id>: realpath(<home>/soul) }` and refuses a later registration whose
|
|
256
|
+
owner resolves to a different path (`E_OWNER stable owner ID already identifies
|
|
257
|
+
a different soul in this state namespace`).
|
|
258
|
+
|
|
259
|
+
Under v2 that path is no longer your checkout. `oats spawn` fetches the soul
|
|
260
|
+
from its member repo at the confirmed commit into the deployment's
|
|
261
|
+
**per-commit soul cache**, `agents/<name>/souls/<commit12>/` (immutable once
|
|
262
|
+
written; `agents/<name>/soul` is a kernel-swapped pointer to the current one),
|
|
263
|
+
and the instance's `<home>/soul` links **its own commit's directory** — so the
|
|
264
|
+
realpath the hook pins is `<deployment>/agents/<name>/souls/<commit12>`, which
|
|
265
|
+
never equals the 0.24 pin (`<repo>/agents/<name>/soul`) and changes whenever the
|
|
266
|
+
member moves. Two consequences:
|
|
267
|
+
|
|
268
|
+
- **Do not reuse the 0.24 `state-dir`.** Its `owners.json` pins every owner to
|
|
269
|
+
the old path; the first v2 spawn of each soul would be refused with `E_OWNER`.
|
|
270
|
+
Give the rebuilt deployment a fresh `state-dir` (§5) and a fresh
|
|
271
|
+
`bindings-file` if the old one names the old state root. The old `state-dir`
|
|
272
|
+
is **frozen custody**: read-only history (`oats okf inspect --source
|
|
273
|
+
<old-state>/sources/<id>/source.json …` still works against it), never edited,
|
|
274
|
+
never re-pointed at the new soul path. Accepted knowledge is not affected —
|
|
275
|
+
it lives in the bases, not in `state-dir`.
|
|
276
|
+
- **The owner pin is per commit.** OKF 2.1.3 records the realpath at first
|
|
277
|
+
registration and the kernel keeps that commit directory for as long as any
|
|
278
|
+
instance links it, so a running instance's pin stays valid; a *later* spawn of
|
|
279
|
+
the same soul at a newer member commit links a different directory and
|
|
280
|
+
registers under the same owner id → `E_OWNER` again. Until OKF re-bases the
|
|
281
|
+
pin on the owner identity rather than the path (an OKF 2.1.4 item), the
|
|
282
|
+
practical rule is: one `state-dir` per (deployment, soul commit) is safe;
|
|
283
|
+
moving a member that owns knowledge means a fresh `state-dir` for the new
|
|
284
|
+
commit's spawns (the previous one becomes frozen custody, as above). Plan
|
|
285
|
+
knowledge-owning souls' member commits deliberately.
|
|
286
|
+
|
|
287
|
+
## 8. Re-take a retained messaging seat with `spawn --provider`
|
|
288
|
+
|
|
289
|
+
In 0.24, an instance-specific messaging identity (a retained seat) was pinned in
|
|
290
|
+
`oats-config.yaml` under `souls:`. That home is gone; the fact belongs to the
|
|
291
|
+
**spawn**:
|
|
292
|
+
|
|
293
|
+
```bash
|
|
294
|
+
oats spawn release-manager --purpose seat --provider oats.aweb identity.source=/abs/path/to/retained/.aw
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
`--provider <cap> key=value` is repeatable; dotted keys nest. The payload is
|
|
298
|
+
merged after the soul's `messaging:` and the machine's `settings.oats.aweb`, and
|
|
299
|
+
recorded in `instance.json.providers.oats.aweb`, so exactly one instance holds
|
|
300
|
+
the seat while other instances of the soul mint fresh identities.
|
|
301
|
+
|
|
302
|
+
**The value is the path itself.** `oats.aweb` reads `identity.source` as the
|
|
303
|
+
absolute path of the `.aw` directory to retain (it must hold `signing.key`); the
|
|
304
|
+
kernel does not resolve symbolic seat names. Because it is an absolute path it is
|
|
305
|
+
a fact about ONE machine, so its other legal home is `oats-local.yaml`
|
|
306
|
+
(`settings.oats.aweb.identity.source: /abs/path`) — never the workspace file
|
|
307
|
+
(absolute paths are refused there, decision 14). Prefer the spawn form: a
|
|
308
|
+
machine-level setting would give the seat to EVERY instance of every messaging
|
|
309
|
+
soul on that machine, and a seat can be held once. The Desktop's
|
|
310
|
+
confirmed apply carries the same map.
|
|
311
|
+
|
|
312
|
+
## 9. Spawn, and check drift
|
|
313
|
+
|
|
314
|
+
```bash
|
|
315
|
+
oats souls # every non-private soul of every confirmed member, with origin and team
|
|
316
|
+
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
|
|
318
|
+
oats spawn <soul> --purpose x
|
|
319
|
+
oats status # per instance: modules … [member moved since (now @ …)] / [capability no longer present]
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
## What disappears
|
|
323
|
+
|
|
324
|
+
| Gone | Replaced by |
|
|
325
|
+
|---|---|
|
|
326
|
+
| `oats-config.yaml` (and the laptop/workspace/repo config chain, `agent-types`, `capabilities.layers`/`additive`, `souls:`, adopted config templates) | `oats-workspace.yaml` defaults + `soul.yaml` `capabilities:`; `oats-local.yaml` for host settings; `spawn --provider` for per-instance facts |
|
|
327
|
+
| `oats.yaml` | `oats-membership.yaml` |
|
|
328
|
+
| `agents/<name>/soul/` as the tracked soul source | `souls/<name>/` (tracked); `agents/` is deployment state — instance homes and the kernel's per-commit soul cache `agents/<name>/souls/<commit12>/` |
|
|
329
|
+
| `.agents/capabilities/installed/` and `owned/` | nothing is installed; `<instance>/.oats/modules/<cap>/` per instance; member capabilities under `<repo>/capabilities/` |
|
|
330
|
+
| `oats init`, `oats use`, `oats install`, `oats restore`, `oats trust`, `oats list`, `oats catalog`, `oats remove`, `oats migrate`, `oats config` | `oats sync`, `oats package add \| remove`, `oats workspace status`, `oats capabilities`, `oats souls` — each removed verb answers `E_UNKNOWN_COMMAND` naming its replacement |
|
|
331
|
+
| lock v1 / v2 | lock v3 (`packages` only, with `url`, `capabilities`, `approved`) |
|
|
332
|
+
| per-soul `source: git:…@v#…`, `repo:`, `path:` | `from: here \| <repo key> \| package` + `packages:` in the workspace |
|
|
333
|
+
| `imports:` of member souls, `exports:` lists | discovery by convention; `private: true` |
|
|
334
|
+
| `stores.<x>.inherit` | `stores:` in the workspace |
|
|
335
|
+
| `teams:` as the messaging payload | `messaging:`; `teams:` are labels |
|
|
336
|
+
| `@revision` on members | none — members are latest; frozen content is a package |
|
|
337
|
+
| ambient-skill exclusion at launch | the harness starts normally; capability skills are copied to `.agents/skills/<cap>/` |
|
|
338
|
+
|
|
339
|
+
## What is kept
|
|
340
|
+
|
|
341
|
+
Kernel-neutral provider payloads and the `binding` contract; per-version
|
|
342
|
+
executable approval (now in the lock); spawn preview / confirmed apply
|
|
343
|
+
(`decision.revision`, now binding the resolution revision) and idempotency;
|
|
344
|
+
retirement and retention; the official catalog; the canonical-plus-alias
|
|
345
|
+
instance construction (`CLAUDE.md → AGENTS.md`, `.claude/skills →
|
|
346
|
+
../.agents/skills`); every published Desktop CLI contract, extended as described
|
|
347
|
+
in [desktop-cli-api.md](desktop-cli-api.md#workspace-model-workspaceapi-2).
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# OATS v0.25.0 — the workspace model (breaking)
|
|
2
|
+
|
|
3
|
+
Kernel/Pi **0.25.0**. Tag `v0.25.0` → 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
|
+
**This is the 0.25 line: a new deployment model, not a patch to the old one.**
|
|
8
|
+
0.24.x keeps spawning 0.24.x deployments; there is no converter. The rebuild
|
|
9
|
+
guide is `docs/rebuild-to-v2.md`. Design: `docs/design/2026-09-23-simplified-workspace-model.md`;
|
|
10
|
+
decision record (26 decisions): `agents/oats-expert/soul/knowledge/decisions/workspace-model-v2.md`;
|
|
11
|
+
normative module contracts: `docs/design/2026-09-23-workspace-module-contracts.md`.
|
|
12
|
+
|
|
13
|
+
## The one idea
|
|
14
|
+
|
|
15
|
+
An organisation has **one workspace**: a Git repo hosting `oats-workspace.yaml`
|
|
16
|
+
that lists its **member repositories**, pins its **packages**, names its
|
|
17
|
+
**teams** and declares **defaults**. Every member carries an
|
|
18
|
+
`oats-membership.yaml` back-link; the handshake is confirmed over the Git
|
|
19
|
+
remotes in the operator's own access context and **membership is the trust**.
|
|
20
|
+
A member exports **souls** (`souls/<name>/soul.yaml`) and **capabilities**
|
|
21
|
+
(`capabilities/<name>/oats.json`) at its latest commit; a soul says where each
|
|
22
|
+
capability comes from (`from: <member repo> | here | package`). Packages are
|
|
23
|
+
consumed only through `packages:` + `oats-lock.json` v3, with **per-version
|
|
24
|
+
executable approval**. A spawn **resolves** the soul (`lib/resolve.mjs`) and
|
|
25
|
+
**materializes** every capability whole into the instance
|
|
26
|
+
(`<home>/.oats/modules/<cap>/`, skills under `<home>/.agents/skills/<cap>/`);
|
|
27
|
+
the harness starts normally. Nothing is installed anywhere.
|
|
28
|
+
|
|
29
|
+
## Kernel — `workspace-v2` (`workspaceApi: 2`), `instance-modules`, `spawn-provider-payload`
|
|
30
|
+
|
|
31
|
+
- **Declarations** (`docs/*.schema.json`, all `schemaVersion: 2`):
|
|
32
|
+
`oats-workspace.yaml` (members, packages, teams, defaults incl. `byTeam`,
|
|
33
|
+
stores, `messaging` incl. `byTeam.<label>`, external souls),
|
|
34
|
+
`oats-membership.yaml` (`workspace`, `team?`), `soul.yaml` v2
|
|
35
|
+
(`capabilities: { <cap>: { from } | off }`, `team`, `private`,
|
|
36
|
+
`compatibility`, slot payloads), `oats-local.yaml` (the ONLY per-machine
|
|
37
|
+
file: `workspace:` or `standalone:` ref, `clones`, `settings.<cap>`,
|
|
38
|
+
`souls.disabled`).
|
|
39
|
+
- **`oats sync`** — discover → confirm membership → resolve packages → write
|
|
40
|
+
`oats-lock.json` v3; unapproved executables listed, exit 2 non-TTY / prompt
|
|
41
|
+
on a TTY. `oats package add|remove`, `oats workspace status`,
|
|
42
|
+
`oats capabilities`, `oats souls` (origin + team columns).
|
|
43
|
+
- **`oats spawn`** — discovers and resolves over the remotes, fetches the soul
|
|
44
|
+
from its member repo (refreshed per commit), materializes, launches.
|
|
45
|
+
`--preview` works before any apply and reports `modules[]` (with
|
|
46
|
+
`changedSince`), `team`, `resolution`, `workspace`, `soulFetched`; the
|
|
47
|
+
decision binds the resolution revision (a member that moved between preview
|
|
48
|
+
and apply → `E_DECISION_STALE`). `--provider <cap> k=v` records a per-spawn
|
|
49
|
+
provider payload (`instance.json.providers`) — the third payload home after
|
|
50
|
+
the soul and `oats-local.yaml settings`.
|
|
51
|
+
- **`instance.json`** carries `modules{}` (name → from/commit/digest),
|
|
52
|
+
`providers{}`, `workspace{ key, commit, resolution, standalone, soul }`,
|
|
53
|
+
`capabilities[]`.
|
|
54
|
+
- **`oats status`** shows drift per instance (`member moved since …`,
|
|
55
|
+
`capability no longer present`); `--json` `instances[].modules[]`.
|
|
56
|
+
- **`oats onboard [<dir>] --workspace <ref>`** — writes `oats-local.yaml`,
|
|
57
|
+
runs the sync path, prints the taught layout and the **hosting rule** for
|
|
58
|
+
mixed public/private organisations (`onboardApi: 2`). Creates no soul,
|
|
59
|
+
spawns nothing.
|
|
60
|
+
- **Standalone case** — a member whose workspace cannot be read (access
|
|
61
|
+
failure only, never network/timeout) still offers its souls with `from: here`
|
|
62
|
+
capabilities **plus `oats.core`** from the official catalog through the
|
|
63
|
+
operator's own lock; marked `standalone: true` in sync/status/spawn output.
|
|
64
|
+
- **Teams** are labels; per-team provider payload is `messaging.byTeam.<label>`
|
|
65
|
+
(merged by the kernel, stripped before the provider). A store names a
|
|
66
|
+
repository; the root inside it is the provider's binding key.
|
|
67
|
+
- **Scheduled spawns** on a workspace deployment materialize exactly like
|
|
68
|
+
`oats spawn` (the scheduler delegates to the CLI).
|
|
69
|
+
- **Security** (found by adversarial review, all pinned): remote tree names
|
|
70
|
+
fsck'd (no traversal), tag naming a blob refused as a commit, symlinks in
|
|
71
|
+
fetched trees refused except the soul's `CLAUDE.md → AGENTS.md` alias,
|
|
72
|
+
`__proto__`/`constructor`/`prototype` refused at every payload layer and in
|
|
73
|
+
`--provider` flags, `byTeam` reserved outside `workspace.messaging`, package
|
|
74
|
+
executables gated on per-version approval, atomic staging with full
|
|
75
|
+
rollback (no half homes).
|
|
76
|
+
|
|
77
|
+
## Removed
|
|
78
|
+
|
|
79
|
+
`oats init`, `use`, `install`, `trust`, `list`, `remove`, `migrate`, `catalog`,
|
|
80
|
+
`update`, `config`, `inject` → typed `E_UNKNOWN_COMMAND` naming the
|
|
81
|
+
replacement. `oats-config.yaml` is no longer read as configuration (a leftover
|
|
82
|
+
one is harmless); the `installed/` tier, `oats.yaml`, `lockfileVersion` 2,
|
|
83
|
+
per-soul `source: git:…` lines, `stores.inherit`, `imports`. The `catalog`
|
|
84
|
+
feature name is gone from `version --json`. `docs/configuration.md` now
|
|
85
|
+
describes `oats-local.yaml` only.
|
|
86
|
+
|
|
87
|
+
## Desktop
|
|
88
|
+
|
|
89
|
+
The Desktop server's `oats catalog` reader now surfaces `E_USAGE` (the verb is
|
|
90
|
+
removed); the Capabilities view's package acquisition flow will follow the new
|
|
91
|
+
DTOs in a later release (Phase F). Everything else in the Desktop is unchanged.
|
|
92
|
+
|
|
93
|
+
## Known follow-ups
|
|
94
|
+
|
|
95
|
+
- The classic (no `oats-local.yaml`) spawn path still runs the pre-v2 compose
|
|
96
|
+
in `lib/core.mjs`; folding it into the one pipeline removes the remaining v1
|
|
97
|
+
modules (residue table in `docs/design/2026-09-23-workspace-v2-implementation-plan.md`).
|
|
98
|
+
- The OATS framework repositories themselves convert to the model (six expert
|
|
99
|
+
souls, `oats.core`/`oats.setup` rewritten for the new architecture) in 0.26.
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# OATS v0.25.1 — workspace-model fix round
|
|
2
|
+
|
|
3
|
+
Kernel/Pi **0.25.1**. Tag `v0.25.1` → 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.0](v0.25.0.md). Every item below
|
|
9
|
+
is a correctness, security or documentation fix found by the team review of
|
|
10
|
+
the 0.25.0 workspace model; the normative record is the "0.25.1 fix round"
|
|
11
|
+
section of `docs/design/2026-09-23-workspace-module-contracts.md` and the
|
|
12
|
+
matching clarifications in
|
|
13
|
+
`agents/oats-expert/soul/knowledge/decisions/workspace-model-v2.md`.
|
|
14
|
+
|
|
15
|
+
## Kernel
|
|
16
|
+
|
|
17
|
+
- **M1 (high) — a running instance's soul no longer changes under it.** Souls
|
|
18
|
+
are fetched into a per-commit cache `agents/<name>/souls/<commit12>/`
|
|
19
|
+
(immutable); `agents/<name>/soul` is an atomically swapped pointer to the
|
|
20
|
+
current commit; each home links its own commit's directory. A 0.25.0 layout
|
|
21
|
+
is migrated in place on first use. OKF's path-pinned owner stays valid per
|
|
22
|
+
instance.
|
|
23
|
+
- **M2 — SSH remotes are fetched over SSH.** The canonical repo key is
|
|
24
|
+
unchanged; the fetch url honours the ref as written (`git@…`/`ssh://` → SSH,
|
|
25
|
+
`https://` → HTTPS, bare `git:` → HTTPS unless `remoteOptions.transport:
|
|
26
|
+
ssh`). A private repo is no longer probed over HTTPS and silently degraded to
|
|
27
|
+
the standalone view; the standalone fallback now reports the host failure
|
|
28
|
+
that triggered it.
|
|
29
|
+
- **M3 (high) — package approval is re-verified at spawn.** `resolveSoul`
|
|
30
|
+
recomputes the executables digest over the package tree at the locked commit
|
|
31
|
+
and refuses `E_PACKAGE_UNAPPROVED { reason: "digest-mismatch" }` when it
|
|
32
|
+
differs from the approved one; one shared `executablesDigestAt` serves
|
|
33
|
+
`sync` and `resolve`.
|
|
34
|
+
- **M4 — annotated tag OIDs are peeled.** `observeRemote` records the peeled
|
|
35
|
+
commit, never a tag object, in results, locks and `instance.json`.
|
|
36
|
+
- **B2 — `work: workspace` spawns on a v2 deployment.** `./work` is the
|
|
37
|
+
deployment directory (the one holding `oats-local.yaml`); no branch recorded;
|
|
38
|
+
the remedy names `oats-local.yaml`.
|
|
39
|
+
- **B3 — operator-level capability commands from the deployment.**
|
|
40
|
+
`oats <ns> <cmd> … --soul <name>` outside a home resolves exactly as a spawn
|
|
41
|
+
of that soul, fetches the module into `<deployment>/.oats/modules/<cap>@<commit12>/`
|
|
42
|
+
and dispatches there with the soul's merged payload (`oats okf init` before
|
|
43
|
+
any instance exists). `--soul` absent → `E_BAD_ARGS`; unknown namespace →
|
|
44
|
+
`E_UNKNOWN_COMMAND`.
|
|
45
|
+
- **L1 — slot `none` empties the slot.** A soul's `knowledge|messaging|tasks:
|
|
46
|
+
none` drops any layer-bearing capability the workspace defaults contributed
|
|
47
|
+
for that layer; only a layer-bearing capability the soul itself declares next
|
|
48
|
+
to `none` is `E_SLOT_CONFLICT`.
|
|
49
|
+
- **L2 — absolute-path refusal is scoped to ref/path fields.** Team
|
|
50
|
+
descriptions and the opaque messaging payload may contain `/`-rooted text.
|
|
51
|
+
- **L3 — one unsafe deep entry no longer blanks a member's souls.** Depth
|
|
52
|
+
filtering precedes the entry-name safety check.
|
|
53
|
+
- **L4 — listing failures are classified.** `maxBuffer` overflow is not
|
|
54
|
+
reported as `timeout`; an unclassified git listing failure becomes a
|
|
55
|
+
discovery problem row (`E_REMOTE_UNREADABLE { reason: "unknown" }`) instead
|
|
56
|
+
of an abort.
|
|
57
|
+
- **L6 — revision splits declarations from payload.** `revision =
|
|
58
|
+
hash(declRevision, payloadRevision)`; decision binding unchanged; preview can
|
|
59
|
+
report `changed since: declarations | payload | both`.
|
|
60
|
+
|
|
61
|
+
## Documentation
|
|
62
|
+
|
|
63
|
+
- **M5 — rebuild guide gaps closed** (`docs/rebuild-to-v2.md`): the tracked
|
|
64
|
+
`git mv agents/<name>/soul souls/<name>` step and the tests that enumerate
|
|
65
|
+
soul paths; OKF 2 owner re-registration (fresh `state-dir` for a rebuilt
|
|
66
|
+
deployment, the old one frozen custody); `oats-local.yaml` example with
|
|
67
|
+
`settings.oats.aweb.delivery` and `settings.oats.okf` `state-dir` +
|
|
68
|
+
`bindings-file`; unlabelled souls receive the messaging base payload only;
|
|
69
|
+
per-soul memory-harvest opt-out is not available in OKF 2.1.3 (an OKF 2.1.4
|
|
70
|
+
item).
|
|
71
|
+
- **L7 — decision 13 reach.** Every `pi` launch a 0.25 kernel performs starts
|
|
72
|
+
the harness normally, classic 0.24 homes included (rebuild guide §0,
|
|
73
|
+
`conventions.md`). `oats session recompose` is `E_UNSUPPORTED_MODE` for
|
|
74
|
+
module homes; `session-recompose` stays advertised for classic homes
|
|
75
|
+
(`desktop-cli-api.md`).
|
|
76
|
+
- **L8 — v1 no longer presented as live** in `schedules.md`, `conventions.md`,
|
|
77
|
+
`implementation.md`, `execution-targets.md`, `desktop.md`,
|
|
78
|
+
`desktop-succession.md`, `integrations.md`, `migration-from-oas.md`
|
|
79
|
+
(a 0.24.x procedure), `desktop-cli-api.md` (readiness producers are the 0.24
|
|
80
|
+
tier), `knowledge-migration.md` (`state-dir` is required; four settings) and
|
|
81
|
+
`knowledge.md` (operator-level `oats okf … --soul <x>` from the deployment).
|
|
82
|
+
|
|
83
|
+
## Known follow-ups (unchanged from 0.25.0 unless noted)
|
|
84
|
+
|
|
85
|
+
- The classic (no `oats-local.yaml`) spawn path still runs the pre-v2 compose;
|
|
86
|
+
`composeInstance` still reads `yolo` / `launch-configs` from an
|
|
87
|
+
`oats-config.yaml` chain when one sits above a deployment.
|
|
88
|
+
- The readiness quartet (`readinessApi: 1`) is still produced by the 0.24 tier
|
|
89
|
+
observers; re-basing it on `spawn --preview` / `sync` / `workspace status` is
|
|
90
|
+
a named follow-up.
|
|
91
|
+
- OKF 2's owner pin is per soul path (hence per commit under M1); re-basing it
|
|
92
|
+
on the owner identity is an OKF 2.1.4 item, as is a per-soul harvest opt-out.
|
|
93
|
+
- `oats-local.yaml` `transport:` (M2's per-machine SSH default) needs a schema
|
|
94
|
+
addition before it can be written.
|
package/docs/schedules.md
CHANGED
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
# Schedules
|
|
2
2
|
|
|
3
3
|
A schedule launches an agent, runs an oats command, or wakes an existing
|
|
4
|
-
instance on a cron. Definitions belong to a scope
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
instance on a cron. Definitions belong to a scope and are committable; every
|
|
5
|
+
`oats schedule` command run anywhere inside that scope, including from an
|
|
6
|
+
instance home, reads and writes the same file. On a **workspace deployment**
|
|
7
|
+
(0.25, [workspaces.md](workspaces.md)) the scope is the deployment directory
|
|
8
|
+
— the one holding `oats-local.yaml` and the `agents/` root (the kernel derives
|
|
9
|
+
it as the directory above the agents root; a leftover `oats-config.yaml` that
|
|
10
|
+
declares `team:` would still win, so remove it); scheduled spawns there
|
|
11
|
+
materialize exactly like `oats spawn`. On a classic 0.24 deployment the scope
|
|
12
|
+
is the team workspace (the config level that declares the team, else the
|
|
13
|
+
outermost `oats-config.yaml` level). Execution belongs to the host that holds
|
|
14
|
+
the scope, so a schedule on a registered server keeps running while your laptop
|
|
15
|
+
sleeps.
|
|
10
16
|
|
|
11
17
|
There is no daemon. One host timer (a launchd user agent on macOS, a systemd
|
|
12
18
|
user timer on Linux) runs `oats schedule tick --host` once a minute; the tick
|