@awebai/oats 0.24.12 → 0.25.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.
- package/bin/oats.mjs +936 -2822
- package/docs/capabilities.md +136 -323
- package/docs/configuration.md +68 -533
- 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 +309 -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 +386 -6
- package/docs/desktop-succession.md +3 -4
- package/docs/first-team.md +107 -224
- package/docs/implementation.md +6 -4
- package/docs/integrations.md +45 -44
- package/docs/knowledge-capability-authoring.md +10 -4
- package/docs/knowledge-migration.md +5 -4
- package/docs/knowledge-reference/package-craft.md +11 -3
- package/docs/knowledge.md +24 -8
- package/docs/layers.md +3 -3
- 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 +233 -0
- package/docs/release-notes/v0.24.13.md +51 -0
- package/docs/release-notes/v0.25.0.md +99 -0
- 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 +429 -119
- package/lib/core.mjs +419 -55
- package/lib/instance-resolution.mjs +312 -0
- package/lib/materialize.mjs +580 -0
- package/lib/packages.mjs +501 -1273
- package/lib/remote.mjs +639 -0
- package/lib/resolve.mjs +576 -0
- package/lib/schedule.mjs +194 -34
- package/lib/workspace.mjs +635 -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,233 @@
|
|
|
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
|
+
## 1. Decide the one workspace
|
|
20
|
+
|
|
21
|
+
One workspace per organisation. Pick the repo that **hosts**
|
|
22
|
+
`oats-workspace.yaml` (a dedicated `agents` repo is common; any member can host
|
|
23
|
+
it). Decide the team labels you want (`global`, `engineering`, …) — labels
|
|
24
|
+
organise and may add defaults; they never gate anything.
|
|
25
|
+
|
|
26
|
+
**If any member is private, host the workspace file in a private repo that is
|
|
27
|
+
not a public member.** The workspace file names every member, so whoever can
|
|
28
|
+
read it sees the member list: a public host would publish the private repo's
|
|
29
|
+
name; hosting inside the private member hides the workspace from public
|
|
30
|
+
contributors entirely. A dedicated private repo (`<org>/workspace`) is the
|
|
31
|
+
honest shape. Public contributors who can read a public member but not the
|
|
32
|
+
host still get that member's souls through the standalone case (`from: here`
|
|
33
|
+
capabilities plus `oats.core`), so a public soul stays usable.
|
|
34
|
+
|
|
35
|
+
Two teams that need two different messaging identities (an open-source team
|
|
36
|
+
and a hosted-operations team, say) stay in ONE workspace: `team:` is a label,
|
|
37
|
+
and the provider payload is addressed by label under `messaging.byTeam` (§2).
|
|
38
|
+
|
|
39
|
+
## 2. Write `oats-workspace.yaml` v2 in the host repo
|
|
40
|
+
|
|
41
|
+
Start from the 0.24 file and rewrite it:
|
|
42
|
+
|
|
43
|
+
| 0.24 | v2 |
|
|
44
|
+
|---|---|
|
|
45
|
+
| `schemaVersion: 1` | `schemaVersion: 2` |
|
|
46
|
+
| `members: [{ source: git:… }]` | `members: [git:…]` — plain refs, **no** `@revision` |
|
|
47
|
+
| `imports:` of your **own** repos' souls | delete — member souls are discovered by convention |
|
|
48
|
+
| `imports:` of a **stranger's** soul (with `revision`) | `external: [{ source: git:<repo>@<full OID>, soul: <path> }]` |
|
|
49
|
+
| `teams: { private: per-human }` (the messaging payload) | `messaging: { private: per-human }`; `teams:` now declares **labels** |
|
|
50
|
+
| `defaults.knowledge: { capability, source }` | `defaults.knowledge: { <cap>: { from: package } }` (one entry, or `none`) |
|
|
51
|
+
| per-soul `stores.<x>.inherit` | `stores: { <name>: git:<repo> }` once, here |
|
|
52
|
+
| `catalog:` | delete (bare versions use the official catalog; `OATS_PACKAGE_CATALOG` overrides) |
|
|
53
|
+
| — | `packages: { <id>: <version> \| git:<repo>@<ref> }` — every version your souls used to carry in `source:` lines, **once** |
|
|
54
|
+
| — | `defaults.capabilities: { oats.core: { from: package } }` and whatever every soul should get |
|
|
55
|
+
|
|
56
|
+
```yaml
|
|
57
|
+
schemaVersion: 2
|
|
58
|
+
name: acme
|
|
59
|
+
members:
|
|
60
|
+
- git:github.com/acme/agents
|
|
61
|
+
- git:github.com/acme/platform
|
|
62
|
+
packages:
|
|
63
|
+
oats.framework: v1.1.3
|
|
64
|
+
oats.okf: v2.1.3
|
|
65
|
+
oats.aweb: v1.11.2
|
|
66
|
+
teams:
|
|
67
|
+
global: { description: Org-wide }
|
|
68
|
+
engineering: { description: Platform }
|
|
69
|
+
defaults:
|
|
70
|
+
capabilities: { oats.core: { from: package } }
|
|
71
|
+
knowledge: { oats.okf: { from: package } }
|
|
72
|
+
messaging: { oats.aweb: { from: package } }
|
|
73
|
+
tasks: none
|
|
74
|
+
stores:
|
|
75
|
+
org: git:github.com/acme/knowledge
|
|
76
|
+
messaging:
|
|
77
|
+
private: per-human
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
No absolute paths anywhere (they belong in `oats-local.yaml`). `from:` values
|
|
81
|
+
that name a repo are **canonical keys** — `github.com/acme/agents`, not
|
|
82
|
+
`git:github.com/acme/agents` and not `https://…`.
|
|
83
|
+
|
|
84
|
+
## 3. Add `oats-membership.yaml` to every member (replaces `oats.yaml`)
|
|
85
|
+
|
|
86
|
+
```yaml
|
|
87
|
+
schemaVersion: 2
|
|
88
|
+
workspace: git:github.com/acme/agents
|
|
89
|
+
team: engineering # optional default label for this repo's souls/capabilities
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Delete `oats.yaml`. Its `exports:` lists are gone: every `souls/*/soul.yaml` and
|
|
93
|
+
`capabilities/*/oats.json` is discoverable; add `private: true` to the ones that
|
|
94
|
+
should stay internal. The host repo backlinks to itself like any member.
|
|
95
|
+
|
|
96
|
+
## 4. Edit every `soul.yaml` to v2
|
|
97
|
+
|
|
98
|
+
| 0.24 | v2 |
|
|
99
|
+
|---|---|
|
|
100
|
+
| `schemaVersion: 1` | `schemaVersion: 2` |
|
|
101
|
+
| `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 |
|
|
102
|
+
| `requires.capabilities.<cap>: { source: git:… }` | `<cap>: { from: package }` (published) or `<cap>: { from: here }` / `{ from: <repo key> }` (a member capability) |
|
|
103
|
+
| `source: repo:…` / `path:` | `{ from: here }` |
|
|
104
|
+
| `defaults.capabilities` | fold into `capabilities:`; use `off` to remove a workspace default |
|
|
105
|
+
| `stores.inherit` | delete (stores are declared once in the workspace) |
|
|
106
|
+
| `imports` | delete |
|
|
107
|
+
| `kind`, `type`, `repo`, `runtime`, `model`, `launch-config` | delete — model/runtime/launch config are spawn-time choices; `team:` replaces `type:` as the grouping |
|
|
108
|
+
| `knowledge:` / `messaging:` payload | keep as is (opaque provider payload); `none` empties the slot |
|
|
109
|
+
| — | `compatibility: { <cap>: ">=x.y" }` if you want a floor |
|
|
110
|
+
|
|
111
|
+
```yaml
|
|
112
|
+
schemaVersion: 2
|
|
113
|
+
name: release-manager
|
|
114
|
+
description: Cuts, verifies and announces releases.
|
|
115
|
+
work: worktree
|
|
116
|
+
team: engineering
|
|
117
|
+
capabilities:
|
|
118
|
+
acme-release-tooling: { from: here }
|
|
119
|
+
knowledge:
|
|
120
|
+
owns: release-manager
|
|
121
|
+
reads: [platform-engineer]
|
|
122
|
+
messaging:
|
|
123
|
+
channels: [acme-eng]
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`name` must equal the soul's directory name; `name`, `description` and `work`
|
|
127
|
+
are required. Capabilities the repo exports live at
|
|
128
|
+
`capabilities/<name>/oats.json` — the manifest is unchanged; you may add
|
|
129
|
+
`private: true` / `team:`.
|
|
130
|
+
|
|
131
|
+
## 5. Write `oats-local.yaml` on each machine
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
~/acme-workspace/ # the taught convention: "<name>-workspace"
|
|
135
|
+
├── oats-local.yaml
|
|
136
|
+
├── agents/ # instance homes
|
|
137
|
+
└── platform/ # member clones, only where someone works IN them
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
```yaml
|
|
141
|
+
schemaVersion: 2
|
|
142
|
+
workspace: git:github.com/acme/agents
|
|
143
|
+
settings: # what used to be `settings:` under capabilities.layers.* in oats-config.yaml
|
|
144
|
+
oats.okf:
|
|
145
|
+
bindings-file: /Users/ana/.oats/okf-bindings.json
|
|
146
|
+
state-dir: /Users/ana/.oats/okf
|
|
147
|
+
souls:
|
|
148
|
+
disabled: [data-analyst]
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Move host paths from `oats-config.yaml` `settings:` here; the `souls:` blocks of
|
|
152
|
+
`oats-config.yaml` become `--provider` flags at spawn (step 8). Delete
|
|
153
|
+
`oats-config.yaml`; it is not read. Do not commit `oats-local.yaml`.
|
|
154
|
+
(`oats onboard <dir> --workspace <repo ref>` writes a minimal `oats-local.yaml`
|
|
155
|
+
and runs the first `sync` for you; add `settings:` afterwards.)
|
|
156
|
+
|
|
157
|
+
## 6. `oats sync`
|
|
158
|
+
|
|
159
|
+
From the deployment directory:
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
oats sync
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
It confirms every member (fix any `no-backlink` / `backlink-elsewhere` /
|
|
166
|
+
`cannot-read` row before going on), resolves `packages:` to commits, writes
|
|
167
|
+
`oats-lock.json` (lockfileVersion 3) and asks for executable approval once per
|
|
168
|
+
package version. The 0.24 lock is not read; delete it (`E_LOCK_SCHEMA` names
|
|
169
|
+
it if you leave it in the way).
|
|
170
|
+
|
|
171
|
+
## 7. Approve packages
|
|
172
|
+
|
|
173
|
+
Approval is **per package version, once, in the lock** — no `oats trust`, no
|
|
174
|
+
per-capability approval, no per-operator trust list. `oats sync` on a terminal
|
|
175
|
+
prints every executable (`commands.*` and `hooks.*.command` targets of every
|
|
176
|
+
capability the package provides) and asks `approve <id> <version>? [y/N]`.
|
|
177
|
+
Declined or non-interactive → exit `2`, the lock records the entry
|
|
178
|
+
unapproved, and spawns of souls using it are refused (`E_PACKAGE_UNAPPROVED`)
|
|
179
|
+
until you run `oats sync` in a terminal and say yes. Member capabilities need no
|
|
180
|
+
approval: membership is the trust.
|
|
181
|
+
|
|
182
|
+
## 8. Re-take a retained messaging seat with `spawn --provider`
|
|
183
|
+
|
|
184
|
+
In 0.24, an instance-specific messaging identity (a retained seat) was pinned in
|
|
185
|
+
`oats-config.yaml` under `souls:`. That home is gone; the fact belongs to the
|
|
186
|
+
**spawn**:
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
oats spawn release-manager --purpose seat --provider oats.aweb identity.source=retained:release-seat
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
`--provider <cap> key=value` is repeatable; dotted keys nest. The payload is
|
|
193
|
+
merged after the soul's `messaging:` and the machine's `settings.oats.aweb`, and
|
|
194
|
+
recorded in `instance.json.providers.oats.aweb`, so exactly one instance holds
|
|
195
|
+
the seat while other instances of the soul mint fresh identities. Consult your
|
|
196
|
+
messaging capability's documentation for the exact key it reads. The Desktop's
|
|
197
|
+
confirmed apply carries the same map.
|
|
198
|
+
|
|
199
|
+
## 9. Spawn, and check drift
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
oats souls # every non-private soul of every confirmed member, with origin and team
|
|
203
|
+
oats capabilities # every capability, member (origin: member <key> @ <commit>) or package (package <id> v<ver>)
|
|
204
|
+
oats spawn <soul> --preview # modules[] with from/commit/changedSince, team, resolution revision
|
|
205
|
+
oats spawn <soul> --purpose x
|
|
206
|
+
oats status # per instance: modules … [member moved since (now @ …)] / [capability no longer present]
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## What disappears
|
|
210
|
+
|
|
211
|
+
| Gone | Replaced by |
|
|
212
|
+
|---|---|
|
|
213
|
+
| `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 |
|
|
214
|
+
| `oats.yaml` | `oats-membership.yaml` |
|
|
215
|
+
| `.agents/capabilities/installed/` and `owned/` | nothing is installed; `<instance>/.oats/modules/<cap>/` per instance; member capabilities under `<repo>/capabilities/` |
|
|
216
|
+
| `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 |
|
|
217
|
+
| lock v1 / v2 | lock v3 (`packages` only, with `url`, `capabilities`, `approved`) |
|
|
218
|
+
| per-soul `source: git:…@v#…`, `repo:`, `path:` | `from: here \| <repo key> \| package` + `packages:` in the workspace |
|
|
219
|
+
| `imports:` of member souls, `exports:` lists | discovery by convention; `private: true` |
|
|
220
|
+
| `stores.<x>.inherit` | `stores:` in the workspace |
|
|
221
|
+
| `teams:` as the messaging payload | `messaging:`; `teams:` are labels |
|
|
222
|
+
| `@revision` on members | none — members are latest; frozen content is a package |
|
|
223
|
+
| ambient-skill exclusion at launch | the harness starts normally; capability skills are copied to `.agents/skills/<cap>/` |
|
|
224
|
+
|
|
225
|
+
## What is kept
|
|
226
|
+
|
|
227
|
+
Kernel-neutral provider payloads and the `binding` contract; per-version
|
|
228
|
+
executable approval (now in the lock); spawn preview / confirmed apply
|
|
229
|
+
(`decision.revision`, now binding the resolution revision) and idempotency;
|
|
230
|
+
retirement and retention; the official catalog; the canonical-plus-alias
|
|
231
|
+
instance construction (`CLAUDE.md → AGENTS.md`, `.claude/skills →
|
|
232
|
+
../.agents/skills`); every published Desktop CLI contract, extended as described
|
|
233
|
+
in [desktop-cli-api.md](desktop-cli-api.md#workspace-model-workspaceapi-2).
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# OATS v0.24.13 — schedule history API 3 and the Desktop schedules table
|
|
2
|
+
|
|
3
|
+
Kernel/Pi/Desktop **0.24.13**. Tag `v0.24.13` → the commit carrying these
|
|
4
|
+
notes; the version-bump commit lands after the tag. Consumers gate on
|
|
5
|
+
`oats version --json` `features[]` names and API integers — never on the version.
|
|
6
|
+
|
|
7
|
+
## Kernel — `schedule-read-2` (`scheduleHistoryApi: 3`; `scheduleApi` stays 2)
|
|
8
|
+
|
|
9
|
+
`oats schedule list|show` history had four defects found by the Desktop
|
|
10
|
+
engineer's exact-source review; each is closed under a new advertised name.
|
|
11
|
+
|
|
12
|
+
- **A run's identity is when it was scheduled and started, never its outcome.**
|
|
13
|
+
`runId = sha256(scheduledFor|startedAt|attemptId)[0:24]`; a run's later facts
|
|
14
|
+
update its one row; `transitions[]` keeps the outcome sequence
|
|
15
|
+
(`["started","unknown","ended"]` is one run, not three); `settled`,
|
|
16
|
+
`recordedAt`. Pre-API-3 rows are returned `legacy: true` with `runId: null`
|
|
17
|
+
and are never merged.
|
|
18
|
+
- **Bounded, descriptor-safe state.** Both scope files are `lstat`ed (regular
|
|
19
|
+
file only), opened `O_NOFOLLOW|O_NONBLOCK`, `fstat`-verified (dev+ino) and
|
|
20
|
+
read whole **only within a 1 MiB budget** — over budget is a typed
|
|
21
|
+
`E_SCHEDULE_STATE_OVERSIZE` refusal, never truncated JSON. `list` carries
|
|
22
|
+
`integrity.sources[]`; history is capped at 50 rows at read
|
|
23
|
+
(`history: {status, stored, truncated}`); one job's corrupt history or bad
|
|
24
|
+
identity is its own row and never fails the others.
|
|
25
|
+
- **Subject truth.** `list`/`show` echo the resolved `scope` and canonical `id`;
|
|
26
|
+
a definition whose own `id` differs from its key → `E_SCHEDULE_IDENTITY`; ids
|
|
27
|
+
are validated before any read. Typed refusal details travel through the CLI's
|
|
28
|
+
JSON failure.
|
|
29
|
+
- **Session provenance, never a transcript.** The `transcript` pointer named a
|
|
30
|
+
reader that does not exist and is gone. Every run carries
|
|
31
|
+
`session: {instance|null, home|null, incarnation|null, server|null, delivery}`
|
|
32
|
+
— what the recorder knew at write time. A read-only transcript verb is a
|
|
33
|
+
separate seam (K12), not implied by this API.
|
|
34
|
+
|
|
35
|
+
## Desktop
|
|
36
|
+
|
|
37
|
+
- **Schedules** view: the compact table (enabled, schedule, target, cadence,
|
|
38
|
+
next run, last reported outcome, actions) with **Recent runs** for the
|
|
39
|
+
workspace (≤50), read once on entry and on explicit Refresh — no polling.
|
|
40
|
+
History enables nothing; `ended` is not success and `delivered` is not
|
|
41
|
+
consumption; missing time facts are shown as unreported. Session provenance
|
|
42
|
+
is displayed with a precise unavailable reason — no transcript, terminal or
|
|
43
|
+
link. Remote history and editing a captured wake are shown as honest
|
|
44
|
+
limitations. Gated on `scheduleHistoryApi === 3` and `schedule-read-2`.
|
|
45
|
+
- **Security**: the command-running `GET /api/schedules` is removed (405, no
|
|
46
|
+
command, no default workspace); legacy POST `list|show` aliases run through
|
|
47
|
+
the same strict admission, budgets and two coalesced read slots.
|
|
48
|
+
|
|
49
|
+
## Upgrade
|
|
50
|
+
|
|
51
|
+
`npm i -g @awebai/oats@0.24.13`, then `oats doctor`.
|
|
@@ -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.
|
package/docs/soul.schema.json
CHANGED
|
@@ -1,82 +1,55 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$schema": "
|
|
3
|
-
"$id": "https://oats.dev/schemas/soul-
|
|
4
|
-
"title": "
|
|
5
|
-
"description": "Authored shape only.
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://oats.dev/schemas/soul-v2.json",
|
|
4
|
+
"title": "Soul declaration v2 (soul.yaml)",
|
|
5
|
+
"description": "Authored shape only. A soul names each capability WITH WHERE IT COMES FROM (`from: here | <repo key> | package`) — a location, never a version. Provider payloads (knowledge, messaging, tasks) are opaque to the kernel; `none` empties the slot. Membership, privacy and team semantics are enforced by discovery/resolution, not here.",
|
|
6
6
|
"type": "object",
|
|
7
|
-
"required": ["schemaVersion", "name"],
|
|
7
|
+
"required": ["schemaVersion", "name", "description", "work"],
|
|
8
8
|
"additionalProperties": false,
|
|
9
9
|
"properties": {
|
|
10
|
-
"schemaVersion": { "const":
|
|
11
|
-
"name": { "
|
|
10
|
+
"schemaVersion": { "const": 2 },
|
|
11
|
+
"name": { "$ref": "#/$defs/slug" },
|
|
12
12
|
"description": { "type": "string" },
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
13
|
+
"work": { "enum": ["worktree", "checkout", "directory", "workspace"] },
|
|
14
|
+
"team": { "$ref": "#/$defs/label", "description": "Team label; overrides the repo's default from oats-membership.yaml." },
|
|
15
|
+
"private": { "type": "boolean", "description": "true → not discoverable in the workspace; usable only from its own repo." },
|
|
16
|
+
"capabilities": {
|
|
16
17
|
"type": "object",
|
|
17
|
-
"
|
|
18
|
-
"additionalProperties":
|
|
19
|
-
"properties": {
|
|
20
|
-
"contract": { "type": "string", "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._-]*$" },
|
|
21
|
-
"version": { "type": "integer", "minimum": 1, "maximum": 9007199254740991 },
|
|
22
|
-
"payload": {}
|
|
23
|
-
}
|
|
24
|
-
},
|
|
25
|
-
"teams": {
|
|
26
|
-
"type": "array", "uniqueItems": true,
|
|
27
|
-
"items": { "type": "string", "pattern": "^[a-zA-Z0-9][a-zA-Z0-9._-]*$" }
|
|
18
|
+
"propertyNames": { "$ref": "#/$defs/capabilityName" },
|
|
19
|
+
"additionalProperties": { "$ref": "#/$defs/capabilityChoice" }
|
|
28
20
|
},
|
|
29
|
-
"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
"yolo": { "type": "boolean" }
|
|
21
|
+
"knowledge": { "$ref": "#/$defs/slotPayload" },
|
|
22
|
+
"messaging": { "$ref": "#/$defs/slotPayload" },
|
|
23
|
+
"tasks": { "$ref": "#/$defs/slotPayload" },
|
|
24
|
+
"compatibility": {
|
|
25
|
+
"type": "object",
|
|
26
|
+
"propertyNames": { "$ref": "#/$defs/capabilityName" },
|
|
27
|
+
"additionalProperties": { "type": "string", "minLength": 1 },
|
|
28
|
+
"description": "Optional FLOORS on package versions (semver ranges) — constraints, not sources."
|
|
29
|
+
}
|
|
39
30
|
},
|
|
40
31
|
"$defs": {
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
"source": { "$ref": "#/$defs/source" },
|
|
52
|
-
"settings": { "type": "object" }
|
|
53
|
-
}
|
|
32
|
+
"slug": { "type": "string", "pattern": "^[a-z0-9]+(?:-[a-z0-9]+)*$" },
|
|
33
|
+
"label": { "type": "string", "pattern": "^[a-z0-9][a-z0-9._-]*$" },
|
|
34
|
+
"capabilityName": { "type": "string", "pattern": "^[a-z0-9][a-z0-9._-]*$" },
|
|
35
|
+
"repoKey": { "type": "string", "pattern": "^[^\\s@/][^\\s@]*/[^\\s@]+$" },
|
|
36
|
+
"fromLocation": {
|
|
37
|
+
"anyOf": [
|
|
38
|
+
{ "const": "package" },
|
|
39
|
+
{ "const": "here" },
|
|
40
|
+
{ "$ref": "#/$defs/repoKey" }
|
|
41
|
+
]
|
|
54
42
|
},
|
|
55
|
-
"
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
"
|
|
59
|
-
"properties": {
|
|
60
|
-
"capabilities": {
|
|
61
|
-
"type": "object", "additionalProperties": false,
|
|
62
|
-
"patternProperties": { "^[a-z0-9][a-z0-9._-]*$": { "$ref": "#/$defs/selection" } }
|
|
63
|
-
},
|
|
64
|
-
"knowledge": { "$ref": "#/$defs/requiredProvider" },
|
|
65
|
-
"messaging": { "$ref": "#/$defs/requiredProvider" },
|
|
66
|
-
"tasks": { "$ref": "#/$defs/requiredProvider" }
|
|
67
|
-
}
|
|
43
|
+
"capabilityRef": {
|
|
44
|
+
"type": "object",
|
|
45
|
+
"required": ["from"],
|
|
46
|
+
"additionalProperties": false,
|
|
47
|
+
"properties": { "from": { "$ref": "#/$defs/fromLocation" } }
|
|
68
48
|
},
|
|
69
|
-
"
|
|
70
|
-
|
|
71
|
-
"
|
|
72
|
-
|
|
73
|
-
"type": "object", "additionalProperties": false,
|
|
74
|
-
"patternProperties": { "^[a-z0-9][a-z0-9._-]*$": { "anyOf": [{ "$ref": "#/$defs/selection" }, { "const": false }] } }
|
|
75
|
-
},
|
|
76
|
-
"knowledge": { "$ref": "#/$defs/defaultProvider" },
|
|
77
|
-
"messaging": { "$ref": "#/$defs/defaultProvider" },
|
|
78
|
-
"tasks": { "$ref": "#/$defs/defaultProvider" }
|
|
79
|
-
}
|
|
49
|
+
"capabilityChoice": { "anyOf": [{ "$ref": "#/$defs/capabilityRef" }, { "const": "off" }] },
|
|
50
|
+
"slotPayload": {
|
|
51
|
+
"anyOf": [{ "const": "none" }, { "type": "object" }],
|
|
52
|
+
"description": "Opaque provider payload, or `none` to leave the slot empty."
|
|
80
53
|
}
|
|
81
54
|
}
|
|
82
55
|
}
|