@awebai/oats 0.24.13 → 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 +930 -2820
- 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 +324 -5
- 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.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 +90 -16
- 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
package/docs/workspaces.md
CHANGED
|
@@ -1,154 +1,464 @@
|
|
|
1
|
-
# Workspaces,
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
A
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
A
|
|
25
|
-
|
|
26
|
-
|
|
1
|
+
# Workspaces — one workspace per organisation, members are trust, nothing is installed
|
|
2
|
+
|
|
3
|
+
This is the OATS workspace model (v2, the 0.25 line). It replaces the per-soul
|
|
4
|
+
`source:` grammar, the installed-capability tier and `oats-config.yaml`. The
|
|
5
|
+
normative record is the Decision concept
|
|
6
|
+
`agents/oats-expert/soul/knowledge/decisions/workspace-model-v2.md`; the module
|
|
7
|
+
contracts the kernel is built against are in
|
|
8
|
+
[design/2026-09-23-workspace-module-contracts.md](design/2026-09-23-workspace-module-contracts.md);
|
|
9
|
+
a full worked example (an imaginary company with three teams) is in
|
|
10
|
+
[design/2026-09-23-simplified-workspace-model.md](design/2026-09-23-simplified-workspace-model.md).
|
|
11
|
+
Moving an existing 0.24.x deployment: [rebuild-to-v2.md](rebuild-to-v2.md).
|
|
12
|
+
|
|
13
|
+
## The rule
|
|
14
|
+
|
|
15
|
+
**Every capability an instance runs is copied whole into that instance at
|
|
16
|
+
spawn. A capability comes from one of two kinds of source; only one kind is
|
|
17
|
+
versioned.**
|
|
18
|
+
|
|
19
|
+
| Source kind | `from:` | Versioned | Trust |
|
|
20
|
+
|---|---|---|---|
|
|
21
|
+
| Member repo | `<repo key>` or `here` | no — always the member's **latest** default-branch state | membership (the reciprocal handshake) |
|
|
22
|
+
| Package | `package` | yes — the version pinned in the workspace's `packages:`; `oats-lock.json` records the exact commit + integrity | executables approved **once per version**, recorded in the lock |
|
|
23
|
+
|
|
24
|
+
A soul names each capability **with where it comes from — a location, never a
|
|
25
|
+
version**. The workspace's `packages:` says which version; materialization
|
|
26
|
+
*records* the exact state (repo or package, commit, content digest) in the
|
|
27
|
+
instance's `instance.json`. Resolution is a handshake check plus a lookup —
|
|
28
|
+
never a search.
|
|
29
|
+
|
|
30
|
+
Nothing is installed. There is no installed-capability directory, no activation
|
|
31
|
+
step, no `oats install`/`use`/`init`/`restore`. A fetch cache may exist under
|
|
32
|
+
the OS cache directory as invisible plumbing; no file refers to it.
|
|
33
|
+
|
|
34
|
+
## The files
|
|
35
|
+
|
|
36
|
+
Four declaration files and one lock. Three of them are shared through Git
|
|
37
|
+
(`oats-workspace.yaml`, `oats-membership.yaml`, `soul.yaml`); one is per
|
|
38
|
+
machine (`oats-local.yaml`); the lock (`oats-lock.json`) sits beside
|
|
39
|
+
`oats-local.yaml` and is identical on every machine that synced the same
|
|
40
|
+
workspace commit. Schemas: [`oats-workspace.schema.json`](oats-workspace.schema.json),
|
|
41
|
+
[`oats-membership.schema.json`](oats-membership.schema.json),
|
|
42
|
+
[`soul.schema.json`](soul.schema.json), [`oats-local.schema.json`](oats-local.schema.json),
|
|
43
|
+
[`oats-lock-v3.schema.json`](oats-lock-v3.schema.json). The JSON schemas encode
|
|
44
|
+
shapes; domain rules (declared teams, duplicate members, canonical `from:` keys,
|
|
45
|
+
the two `packages:` value forms) live in the kernel's `validateWorkspace` /
|
|
46
|
+
`validateSoul`, which are the authority.
|
|
47
|
+
|
|
48
|
+
### `oats-workspace.yaml` — the one shared declaration
|
|
49
|
+
|
|
50
|
+
Lives in the repository that **hosts** the workspace (often a dedicated
|
|
51
|
+
`agents` repo, but any member can host it). One per organisation.
|
|
27
52
|
|
|
28
53
|
```yaml
|
|
29
|
-
schemaVersion:
|
|
30
|
-
name:
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
54
|
+
schemaVersion: 2
|
|
55
|
+
name: acme
|
|
56
|
+
|
|
57
|
+
members: # repo refs, NO @revision (E_WORKSPACE_SCHEMA)
|
|
58
|
+
- git:github.com/acme/agents # the host is a member too — it backlinks like any other
|
|
59
|
+
- git:github.com/acme/platform
|
|
60
|
+
- git:github.com/acme/tools # a member that ALSO publishes a package (see below)
|
|
61
|
+
|
|
62
|
+
packages: # the ONLY versioned things
|
|
63
|
+
oats.framework: v1.1.3 # bare version → resolves through the official catalog
|
|
64
|
+
oats.okf: v2.1.3
|
|
65
|
+
acme.tools: git:github.com/acme/tools@v0.4.0 # outside the catalog → git:<repo>@<tag|OID>; still a package
|
|
66
|
+
|
|
67
|
+
teams: # labels, declared once so they cannot drift
|
|
68
|
+
global: { description: Org-wide souls and house capabilities }
|
|
69
|
+
engineering: { description: Platform and release automation }
|
|
70
|
+
|
|
71
|
+
defaults:
|
|
72
|
+
capabilities:
|
|
73
|
+
oats.core: { from: package }
|
|
74
|
+
acme-house-style: { from: github.com/acme/agents } # a CANONICAL repo key: host/path, no scheme, no .git
|
|
75
|
+
knowledge: { oats.okf: { from: package } } # one slot default at most; a soul may say `none`
|
|
76
|
+
messaging: none
|
|
77
|
+
tasks: none
|
|
78
|
+
byTeam:
|
|
79
|
+
engineering:
|
|
80
|
+
capabilities: { acme-release-tooling: { from: github.com/acme/agents } }
|
|
81
|
+
|
|
82
|
+
stores: # knowledge stores, declared once
|
|
83
|
+
org: git:github.com/acme/knowledge
|
|
84
|
+
|
|
85
|
+
messaging: # an opaque provider payload for the messaging slot
|
|
86
|
+
private: per-human
|
|
87
|
+
|
|
88
|
+
external: # souls borrowed from NON-members; revision REQUIRED
|
|
89
|
+
- source: git:github.com/oss-collective/experts@9c4e1f2a9c4e1f2a9c4e1f2a9c4e1f2a9c4e1f2a
|
|
90
|
+
soul: souls/security-reviewer
|
|
38
91
|
```
|
|
39
92
|
|
|
40
|
-
|
|
93
|
+
Refused by the schema: absolute filesystem paths as values anywhere (host state
|
|
94
|
+
belongs in `oats-local.yaml`), `@revision` on members, unknown top-level keys.
|
|
95
|
+
A `from:` value is `package`, `here` (souls only) or a **canonical repo key**
|
|
96
|
+
exactly as the kernel spells it (`parseRepoRef(ref).key`: lowercase host,
|
|
97
|
+
`org/repo`, no scheme, no `git:`, no `.git`; `local/<abs-path>` for a
|
|
98
|
+
file/bare-directory remote). Any other spelling is a schema error at
|
|
99
|
+
validation, not a late membership error.
|
|
41
100
|
|
|
42
|
-
### `oats.yaml` —
|
|
43
|
-
|
|
44
|
-
A repository advertises the souls, package roots and provider-owned knowledge declarations it actually supplies. A member also points back to its workspace:
|
|
101
|
+
### `oats-membership.yaml` — the backlink, in every member
|
|
45
102
|
|
|
46
103
|
```yaml
|
|
47
|
-
schemaVersion:
|
|
48
|
-
workspace:
|
|
49
|
-
|
|
50
|
-
exports:
|
|
51
|
-
souls:
|
|
52
|
-
- path: souls/domain-expert
|
|
53
|
-
definition: souls/domain-expert/soul.yaml
|
|
54
|
-
packages:
|
|
55
|
-
- path: oats-package
|
|
104
|
+
schemaVersion: 2
|
|
105
|
+
workspace: git:github.com/acme/agents # "I am a member of acme"
|
|
106
|
+
team: engineering # optional: default team label for this repo's items
|
|
56
107
|
```
|
|
57
108
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
`oats-workspace.yaml` and `oats.yaml` may coexist. If the workspace host also participates as a member, it is explicitly admitted and has a matching backlink just like another member.
|
|
109
|
+
Nothing else. It replaces `oats.yaml`; there are no export lists.
|
|
61
110
|
|
|
62
|
-
### `soul.yaml` —
|
|
63
|
-
|
|
64
|
-
A portable soul is an authored definition, not a dependency on whatever happens to be installed on its publisher's machine. It contains canonical `AGENTS.md`, a relative `CLAUDE.md` alias, its reviewed skill/resource closure and a versioned declaration.
|
|
65
|
-
|
|
66
|
-
For example, this declaration excerpt requires a particular knowledge capability **and names where it comes from**:
|
|
111
|
+
### `souls/<name>/soul.yaml` — where each capability comes from
|
|
67
112
|
|
|
68
113
|
```yaml
|
|
69
|
-
schemaVersion:
|
|
70
|
-
name:
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
114
|
+
schemaVersion: 2
|
|
115
|
+
name: release-manager
|
|
116
|
+
description: Cuts, verifies and announces releases.
|
|
117
|
+
work: worktree # worktree | checkout | directory | workspace
|
|
118
|
+
team: engineering # optional; else the repo's default; else "unassigned"
|
|
119
|
+
|
|
120
|
+
capabilities:
|
|
121
|
+
acme-release-tooling: { from: here } # `here` = the repo this soul.yaml lives in
|
|
122
|
+
acme-deploy: { from: package } # provided by acme.tools, pinned in packages:
|
|
123
|
+
acme-house-style: off # removes a workspace default
|
|
124
|
+
|
|
125
|
+
knowledge: # provider payload, opaque to the kernel
|
|
126
|
+
owns: release-manager
|
|
127
|
+
reads: [platform-engineer]
|
|
128
|
+
messaging:
|
|
129
|
+
channels: [acme-eng]
|
|
130
|
+
tasks: none # empties the slot
|
|
131
|
+
|
|
132
|
+
compatibility: # optional FLOORS on package versions — constraints, not sources
|
|
133
|
+
oats.okf: ">=2.1"
|
|
75
134
|
```
|
|
76
135
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
- `git:` selects versioned software from a repository/package root. `repo:` refers to a contained path in the declaring source repository, not the caller's working directory. `path:` is an explicitly authorised local development choice, not a remotely portable ambient fallback.
|
|
83
|
-
- Capability IDs alone do not establish software origin. An old `from: installed` config entry is not a substitute for a portable source declaration.
|
|
84
|
-
- A source may contain authored knowledge snapshots or resources, but retained artifacts are not writable knowledge stores. Learning locations and procedures belong to the selected capability.
|
|
85
|
-
|
|
86
|
-
See the [soul schema](soul.schema.json) and [declaration contract](design/2026-09-15-portable-declarations.md). Classic fields such as `kind`, `type` and a machine-local `repo` are not portable declaration fields; do not relabel an old file without validating it.
|
|
136
|
+
Beside it: `AGENTS.md` (canonical), `CLAUDE.md → AGENTS.md`, `skills/`. Every
|
|
137
|
+
`souls/*/soul.yaml` in a member is discoverable; one that wants to stay
|
|
138
|
+
internal says `private: true` (spawnable only from its own repo). A soul's
|
|
139
|
+
`name` must equal its directory name; the first of two souls declaring one
|
|
140
|
+
name (by path) is listed, the second is a problem.
|
|
87
141
|
|
|
88
|
-
|
|
142
|
+
### `capabilities/<name>/oats.json` — the manifest, unchanged shape
|
|
89
143
|
|
|
90
|
-
|
|
144
|
+
The capability manifest is the one file that did not change (see
|
|
145
|
+
[capabilities.md](capabilities.md)). Discovery relies on `capability` (the same
|
|
146
|
+
`^[a-z0-9][a-z0-9._-]*$` grammar every `capabilities:` key uses), `version`,
|
|
147
|
+
`layer`, and may read `private: true` and `team: <label>`. `version` is
|
|
148
|
+
informational for member capabilities — a materialized copy is identified by
|
|
149
|
+
its content digest.
|
|
91
150
|
|
|
92
|
-
|
|
151
|
+
### `oats-local.yaml` — the only per-machine file
|
|
93
152
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
-
|
|
103
|
-
|
|
104
|
-
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
Provider-owned declarations remain opaque to the kernel until the selected provider interprets them through its contract. There is no portable repository-level capability policy tier silently inherited from the publisher, and no mandatory agent-type hierarchy replacing a soul's own requirements.
|
|
108
|
-
|
|
109
|
-
Repository briefing/worktree setup remains work-target behavior with its own supported authority. Merely placing a repository `AGENTS.md` nearby does not guarantee it is composed into every harness's instructions.
|
|
153
|
+
```yaml
|
|
154
|
+
schemaVersion: 2
|
|
155
|
+
workspace: git:github.com/acme/agents # observed over the remote; need not be cloned
|
|
156
|
+
clones: # optional: where member clones live, if not the convention
|
|
157
|
+
github.com/acme/platform: /Users/ana/src/acme-platform
|
|
158
|
+
settings: # host-owned values the manifests ask for
|
|
159
|
+
oats.okf:
|
|
160
|
+
bindings-file: /Users/ana/.oats/okf-bindings.json
|
|
161
|
+
state-dir: /Users/ana/.oats/okf
|
|
162
|
+
souls:
|
|
163
|
+
disabled: [data-analyst] # not run on this machine
|
|
164
|
+
```
|
|
110
165
|
|
|
111
|
-
|
|
166
|
+
See [configuration.md](configuration.md). `oats-config.yaml` no longer exists.
|
|
167
|
+
|
|
168
|
+
### `oats-lock.json` — lock v3
|
|
169
|
+
|
|
170
|
+
Written by `oats sync`; the only persisted state at the deployment besides
|
|
171
|
+
`oats-local.yaml`. See [packages.md](packages.md).
|
|
172
|
+
|
|
173
|
+
## Membership and trust
|
|
174
|
+
|
|
175
|
+
**Reciprocal membership is a hard gate.** A repo is a member only when
|
|
176
|
+
`oats-workspace.yaml` lists it **and** the repo's `oats-membership.yaml` names
|
|
177
|
+
the workspace back. One file per side; a copied backlink in a fork, or a folder
|
|
178
|
+
with the right name, is not admission.
|
|
179
|
+
|
|
180
|
+
**Membership is the whole trust decision for member capabilities** — the same
|
|
181
|
+
model as a repo's committed `.agents/skills/`: whoever can push to the repo
|
|
182
|
+
decides what runs, and the branch's latest state is what runs. No per-operator
|
|
183
|
+
trust lists, no per-capability approval for members. Packages come from
|
|
184
|
+
*outside* that boundary and keep a one-time executable approval per version.
|
|
185
|
+
|
|
186
|
+
**The handshake is observed with the operator's own Git read access, in one
|
|
187
|
+
access context.** The kernel reads both halves over the remotes
|
|
188
|
+
(`git ls-remote`, shallow fetches, the operator's own credential helpers,
|
|
189
|
+
never a prompt). A half that cannot be read makes the member *unconfirmed*,
|
|
190
|
+
never a half-success. `oats workspace status` and `oats sync` show each member
|
|
191
|
+
as `confirmed` or the reason it is not:
|
|
192
|
+
|
|
193
|
+
| status | meaning |
|
|
194
|
+
|---|---|
|
|
195
|
+
| `confirmed` | listed and backlinks to this workspace |
|
|
196
|
+
| `not-listed` | the workspace does not list the repo |
|
|
197
|
+
| `no-backlink` | no (or invalid) `oats-membership.yaml` at the member's default branch |
|
|
198
|
+
| `backlink-elsewhere` | the member names a different workspace (a case-only difference is flagged: repo paths are case-sensitive identities) |
|
|
199
|
+
| `cannot-read` | the operator cannot read the member (auth / not-found / network / timeout) |
|
|
200
|
+
|
|
201
|
+
An unconfirmed member contributes nothing but its row: its souls are invisible,
|
|
202
|
+
its capabilities unresolvable (`E_NOT_A_MEMBER` / `E_MEMBERSHIP_UNCONFIRMED`).
|
|
203
|
+
Reading the workspace repo *is* being in the workspace — a workspace's access
|
|
204
|
+
control is Git's.
|
|
205
|
+
|
|
206
|
+
**Private items.** `private: true` on a soul or a capability keeps it out of the
|
|
207
|
+
workspace listing; a private capability is usable only by souls of the same
|
|
208
|
+
repo (`E_CAPABILITY_PRIVATE` otherwise). Owners still see their own private
|
|
209
|
+
items.
|
|
210
|
+
|
|
211
|
+
**External souls.** `external:` adopts a soul by reference from a repo that is
|
|
212
|
+
**not** a member, pinned to a full commit. No handshake is asked for and none is
|
|
213
|
+
read; the soul gets no member-tier capabilities of its own repo; it is
|
|
214
|
+
"source-complete" (its skills travel with it) and the workspace's defaults fill
|
|
215
|
+
its slots. An `external[].team` overrides the soul's own `team`.
|
|
216
|
+
|
|
217
|
+
## Member tier vs package tier — the non-collapse rule
|
|
218
|
+
|
|
219
|
+
A repository may be a **member** (it completed the handshake; its `souls/*` and
|
|
220
|
+
`capabilities/*` are member-tier: latest state, trusted by membership) **and** a
|
|
221
|
+
**package publisher** (its `oats-package/` is consumed only through
|
|
222
|
+
`packages:`: versioned, locked, approved). The two never collapse:
|
|
223
|
+
|
|
224
|
+
- `from: <repo key>` looks **only** under `<repo>/capabilities/<name>/oats.json`
|
|
225
|
+
at the member's latest state. It never looks inside `oats-package/`. A name
|
|
226
|
+
that exists only inside the repo's package fails with `E_CAPABILITY_MISSING`
|
|
227
|
+
and the hint `provided by package <id>; use from: package`.
|
|
228
|
+
- `from: package` looks **only** in the lock (which package provides the
|
|
229
|
+
capability). It never looks at member capabilities, even when the package's
|
|
230
|
+
repo is a member.
|
|
231
|
+
- Discovery reports a member's `oats-package/` as `publishes: { package,
|
|
232
|
+
version }` on the member row (informational) and does **not** list the
|
|
233
|
+
package's capabilities as member capabilities.
|
|
234
|
+
|
|
235
|
+
So the framework's own souls say `oats.okf: { from: package }` even though
|
|
236
|
+
`oats-okf` is a member of the OATS workspace — and every package repo carries a
|
|
237
|
+
member soul that is the expert in that capability (`okf-expert`, `aweb-expert`,
|
|
238
|
+
…), discoverable at latest state like any member soul.
|
|
239
|
+
|
|
240
|
+
## Packages, lock, approval, catalog
|
|
241
|
+
|
|
242
|
+
`packages:` values have exactly two forms:
|
|
243
|
+
|
|
244
|
+
- a **bare version** (`v2.1.3`, `2.1.3`, `v1.0.0-rc.1`) — resolved through the
|
|
245
|
+
official catalog (`package-catalog.json` in the `oats` repo; the reviewed
|
|
246
|
+
marketplace, see [official-marketplace.md](official-marketplace.md)). This is
|
|
247
|
+
the only way a package becomes *pinnable by id*.
|
|
248
|
+
- **`git:<repo>@<ref>`** — a direct package ref: `<repo>` is any ref the kernel
|
|
249
|
+
understands (`github.com/org/repo`, `https://…`, `git@host:…`, `/abs/bare.git`,
|
|
250
|
+
`file:///…`), `<ref>` a tag name or a full commit OID. The package is read at
|
|
251
|
+
`oats-package/` inside that repo.
|
|
252
|
+
|
|
253
|
+
Both are packages: versioned, locked, approved. A ref that resolves to a
|
|
254
|
+
**branch** is refused (`E_PACKAGE_INTEGRITY { why: "branch" }`) — versions are
|
|
255
|
+
immutable. A tag that moved (same version string, different commit) fails
|
|
256
|
+
integrity on the next `oats sync` and asks again.
|
|
257
|
+
|
|
258
|
+
`oats sync` confirms membership, resolves every `packages:` entry to a commit +
|
|
259
|
+
content digest, asks (on a terminal) for any missing per-version executable
|
|
260
|
+
approval, writes `oats-lock.json` (lockfileVersion 3) and reports what changed.
|
|
261
|
+
`oats package add <id> <version|git:…@…>` / `oats package remove <id>` edit
|
|
262
|
+
`packages:` in the workspace file when it is tracked by the current checkout,
|
|
263
|
+
else print the line to add — the workspace file is shared through Git. Details:
|
|
264
|
+
[packages.md](packages.md).
|
|
265
|
+
|
|
266
|
+
## Resolution, spelled out
|
|
267
|
+
|
|
268
|
+
For each `(name, from)` in
|
|
269
|
+
`defaults.<slot>` ⊕ `defaults.capabilities` ⊕ `defaults.byTeam[<soul team>]` ⊕
|
|
270
|
+
`soul.capabilities` (later wins; `off` removes; a soul `<slot>: none` drops
|
|
271
|
+
the workspace's slot default):
|
|
112
272
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
273
|
+
```
|
|
274
|
+
from: package → some locked package provides `name` else E_PACKAGE_MISSING (run `oats sync`)
|
|
275
|
+
→ that package version is approved else E_PACKAGE_UNAPPROVED
|
|
276
|
+
→ read its capability manifest at the locked commit; copy; record package/version/commit/digest
|
|
277
|
+
from: <repo> → <repo> is a CONFIRMED member else E_NOT_A_MEMBER / E_MEMBERSHIP_UNCONFIRMED
|
|
278
|
+
(or `here`) → it has capabilities/<name>/oats.json else E_CAPABILITY_MISSING
|
|
279
|
+
→ not private, unless <repo> is the soul's own else E_CAPABILITY_PRIVATE
|
|
280
|
+
→ copy from the member's current state; record repo/commit/digest
|
|
281
|
+
|
|
282
|
+
slots a module whose manifest says `layer: X` fills slot X; two → E_SLOT_CONFLICT;
|
|
283
|
+
a slot default must be a capability of that layer; soul `X: none` empties X
|
|
284
|
+
skills duplicate names within the composed set → E_SKILL_DUPLICATE (names both capabilities)
|
|
285
|
+
floors soul.compatibility.<cap> is checked against the package's version → E_COMPATIBILITY
|
|
286
|
+
```
|
|
119
287
|
|
|
120
|
-
|
|
288
|
+
The result is an immutable **resolution** with a `revision` (a hash of
|
|
289
|
+
everything above); the spawn decision binds it, so a member that moved between
|
|
290
|
+
preview and apply is `E_DECISION_STALE`, not a silent drift.
|
|
121
291
|
|
|
122
|
-
|
|
292
|
+
## Materialization and the instance home
|
|
123
293
|
|
|
124
|
-
|
|
294
|
+
At spawn every resolved capability is **copied whole** into the instance home —
|
|
295
|
+
skills, injects, scripts, hooks — from the remote at the recorded commit.
|
|
296
|
+
Nothing is symlinked, nothing is shared between instances.
|
|
125
297
|
|
|
126
|
-
|
|
298
|
+
```
|
|
299
|
+
<agents-root>/<soul>/instances/<instance>/
|
|
300
|
+
├── AGENTS.md # composed: soul AGENTS.md + kernel/work-mode blocks + each module's inject
|
|
301
|
+
├── CLAUDE.md → AGENTS.md
|
|
302
|
+
├── .agents/skills/<capability>/<skill>/SKILL.md # full copies; where pi/codex look
|
|
303
|
+
├── .claude/skills → ../.agents/skills
|
|
304
|
+
├── .oats/modules/<capability>/ # the full capability copy: oats.json, bin/, injects/, skills/
|
|
305
|
+
├── instance.json # modules{}, providers{}, workspace{} recorded here
|
|
306
|
+
├── soul → <agents-root>/<soul>/soul # read-only reference
|
|
307
|
+
├── TASK.md
|
|
308
|
+
└── work/
|
|
309
|
+
```
|
|
127
310
|
|
|
128
|
-
|
|
311
|
+
`instance.json.modules.<cap>` records `from` (`{ kind: "member", repoKey,
|
|
312
|
+
commit }` or `{ kind: "package", package, version, commit, integrity, repoKey }`),
|
|
313
|
+
`commit`, `digest` (sha256 of the copied tree) and `materializedAt`;
|
|
314
|
+
`instance.json.providers.<cap>` records the merged provider payload. A running
|
|
315
|
+
instance never changes under itself: a member moving or `packages:` being
|
|
316
|
+
bumped affects only new spawns. Details and DTOs:
|
|
317
|
+
[souls-and-instances.md](souls-and-instances.md), [desktop-cli-api.md](desktop-cli-api.md).
|
|
318
|
+
|
|
319
|
+
**Drift is shown, not prevented.** `oats status` compares each instance's
|
|
320
|
+
recorded modules with the workspace's current picture: `current`, `moved`
|
|
321
|
+
(member or package now at another commit) or `missing` (capability no longer
|
|
322
|
+
present, member unconfirmed, package no longer locked). `oats spawn --preview`
|
|
323
|
+
lists `changedSince` the newest previous instance of the same soul.
|
|
324
|
+
|
|
325
|
+
**Harnesses start normally.** OATS is a skill contributor, not a skill sandbox:
|
|
326
|
+
cwd = the instance home, the harness's own skill discovery intact
|
|
327
|
+
(`~/.pi/agent/skills`, `.agents/skills` up the tree, `.claude/`, …); machine-
|
|
328
|
+
and repo-level skills resolve exactly as they would without OATS. OATS
|
|
329
|
+
composes instructions (`AGENTS.md`) and pins model/provider settings; it does
|
|
330
|
+
not exclude anything.
|
|
331
|
+
|
|
332
|
+
## Teams
|
|
333
|
+
|
|
334
|
+
`teams:` declares labels once (`global`, `engineering`, …) so they cannot drift
|
|
335
|
+
into typos. A soul or capability carries `team:`, else its repo's default from
|
|
336
|
+
`oats-membership.yaml`, else `unassigned`. A label not declared in `teams:` is
|
|
337
|
+
`E_TEAM_UNKNOWN` (the item is still listed). `defaults.byTeam.<team>.capabilities`
|
|
338
|
+
adds capabilities additively for souls with that label (`off` removes). **A
|
|
339
|
+
label never gates, restricts, changes trust or partitions the knowledge
|
|
340
|
+
store** — it organises and can supply defaults. The messaging provider's payload
|
|
341
|
+
(private teams, channels) lives under `messaging:`, so "team" means one thing.
|
|
342
|
+
|
|
343
|
+
## Provider payloads have three homes
|
|
344
|
+
|
|
345
|
+
| What it is | Where | Example |
|
|
346
|
+
|---|---|---|
|
|
347
|
+
| True of every instance of the soul | `soul.yaml` → `knowledge:` / `messaging:` / `tasks:` | `knowledge: { owns: release-manager }` |
|
|
348
|
+
| A fact about this machine | `oats-local.yaml` → `settings.<cap>.<key>` (absolute paths are refused in the workspace file) | `settings.oats.okf.state-dir: /Users/ana/.oats/okf` |
|
|
349
|
+
| A fact about **this spawn** | `oats spawn … --provider <cap> key=value` (repeatable; dotted keys nest) → `instance.json.providers.<cap>` | `--provider oats.aweb identity.source=retained:release-seat` |
|
|
129
350
|
|
|
130
|
-
|
|
351
|
+
The merged payload is `workspace.messaging` (messaging slot only; its base
|
|
352
|
+
keys ⊕ `byTeam[<soul's team>]`, with `byTeam` itself stripped) ⊕ soul slot
|
|
353
|
+
payload ⊕ `local.settings[cap]` ⊕ `spawn.providers[cap]` — objects deep-merge,
|
|
354
|
+
later wins on scalars and arrays. The provider's own `binding` contract
|
|
355
|
+
(`normalize → bind → check`) runs over the merged payload exactly as before.
|
|
356
|
+
Two teams, two messaging identities, one workspace:
|
|
131
357
|
|
|
132
|
-
|
|
358
|
+
```yaml
|
|
359
|
+
teams: { oss: { description: Open protocol }, cloud: { description: Hosted application } }
|
|
360
|
+
messaging:
|
|
361
|
+
byTeam:
|
|
362
|
+
oss: { team: aweb:example.oss }
|
|
363
|
+
cloud: { team: aweb:example.cloud }
|
|
364
|
+
```
|
|
133
365
|
|
|
134
|
-
|
|
366
|
+
A soul with `team: cloud` hands its messaging provider `{ team: aweb:example.cloud, … }`;
|
|
367
|
+
a label under `byTeam` that is not declared in `teams:` is `E_WORKSPACE_SCHEMA`.
|
|
368
|
+
A store (`stores: { <name>: <repo ref> }`) names a repository; where the base
|
|
369
|
+
lives inside it is the knowledge provider's own binding key (`root` for OKF),
|
|
370
|
+
given in the payload — a repo ref never carries a `#path`.
|
|
135
371
|
|
|
136
|
-
|
|
372
|
+
`--provider` for a capability the soul does not resolve is `E_CAPABILITY_MISSING`;
|
|
373
|
+
`__proto__`/`constructor`/`prototype` as a key at any depth is refused.
|
|
137
374
|
|
|
138
|
-
##
|
|
375
|
+
## Discovery over remotes and the `<name>-workspace/` convention
|
|
139
376
|
|
|
140
|
-
|
|
377
|
+
Discovery and resolution work against **Git remotes, never local clones**. The
|
|
378
|
+
kernel fetches `oats-workspace.yaml`, each member's `oats-membership.yaml`,
|
|
379
|
+
every `souls/*/soul.yaml` and `capabilities/*/oats.json`, and every package by
|
|
380
|
+
URL — with the operator's own git configuration (`GIT_TERMINAL_PROMPT=0`, ssh in
|
|
381
|
+
BatchMode: nothing ever prompts). Neither the repo that defines a capability nor
|
|
382
|
+
the repo that hosts the workspace needs to be cloned.
|
|
141
383
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
-
|
|
384
|
+
**The only thing that needs a clone is a soul's work target** (`work:
|
|
385
|
+
worktree | checkout`). Spawning a soul whose repo is not yet cloned is a guided
|
|
386
|
+
clone-then-spawn, a job for the onboarding skill, not the kernel.
|
|
145
387
|
|
|
146
|
-
|
|
388
|
+
The taught default is one folder named after the workspace:
|
|
147
389
|
|
|
148
|
-
|
|
390
|
+
```
|
|
391
|
+
~/acme-workspace/ ← "<name>-workspace"
|
|
392
|
+
├── oats-local.yaml ← which workspace this machine realizes + host paths + disabled souls
|
|
393
|
+
├── oats-lock.json ← exact commit + integrity + per-version approval per package
|
|
394
|
+
├── agents/ ← instance homes (each self-contained) + fetched soul sources
|
|
395
|
+
├── platform/ ← clone of github.com/acme/platform (only if someone works IN it)
|
|
396
|
+
└── tools/
|
|
397
|
+
```
|
|
149
398
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
399
|
+
The kernel never depends on this shape: `oats-local.yaml` is found by walking
|
|
400
|
+
up from the current directory (`E_LOCAL_MISSING` otherwise); clones are found
|
|
401
|
+
through `clones:` or the convention. A soul that lives in a member repo is
|
|
402
|
+
fetched into `<agents-root>/<soul>/soul/` at its discovered commit before its
|
|
403
|
+
first spawn (idempotent per commit); its instances then materialize as above.
|
|
404
|
+
|
|
405
|
+
## The standalone case
|
|
406
|
+
|
|
407
|
+
A repo you can read whose workspace you **cannot** read (a contractor with
|
|
408
|
+
access to `platform` but not to the private `agents` repo) still offers its
|
|
409
|
+
souls: their `from: here` capabilities resolve; `from: <other member>` and
|
|
410
|
+
`from: package` are unresolvable (the version list lives in the workspace
|
|
411
|
+
file); workspace defaults do not apply because they cannot be seen. This is the
|
|
412
|
+
workspace's access control working, not a degraded mode to paper over: make
|
|
413
|
+
the host repo readable (it holds declarations, no secrets) or grant access.
|
|
414
|
+
|
|
415
|
+
Two things keep the standalone spawn useful rather than hollow: `oats.core`
|
|
416
|
+
(the framework's own operational package) is the kernel's default here as
|
|
417
|
+
well, resolved from the official catalog through the operator's own lock and
|
|
418
|
+
approved like any package (a soul may say `oats.core: off`); and the
|
|
419
|
+
operator's `oats-local.yaml` may name the repo directly (`workspace: <member
|
|
420
|
+
ref>` — the kernel notices it is a member whose workspace it cannot read and
|
|
421
|
+
falls back to the standalone view — or `standalone: <repo ref>` to ask for
|
|
422
|
+
that view explicitly).
|
|
423
|
+
|
|
424
|
+
**Hosting the workspace file when some members are private.** Everyone who
|
|
425
|
+
can read the workspace file sees the member list. So: a public member never
|
|
426
|
+
hosts it when any member is private (it would publish the private repo's
|
|
427
|
+
name); the private member hosting it hides the workspace from public
|
|
428
|
+
contributors, who then live in the standalone case above. A dedicated private
|
|
429
|
+
repo (`<org>/workspace`) is the honest shape for a mixed organisation; the
|
|
430
|
+
onboarding skill asks this question first.
|
|
431
|
+
|
|
432
|
+
## What is deliberately not versioned
|
|
433
|
+
|
|
434
|
+
- **Members.** A member is always its latest state; there is no `@revision`
|
|
435
|
+
on `members:`. A team that wants frozen capabilities publishes them as a
|
|
436
|
+
package and pins that.
|
|
437
|
+
- **Member capabilities' `version` field** — informational; the content digest
|
|
438
|
+
recorded at spawn identifies a copy.
|
|
439
|
+
- **Souls in members.** A soul is spawned from its repo's current state; the
|
|
440
|
+
commit is recorded in `instance.json.workspace.soul`.
|
|
441
|
+
- **The deployment layout** — the operator's; only the convention is taught.
|
|
442
|
+
|
|
443
|
+
What **is** versioned: `packages:` (the workspace's one list), the lock's exact
|
|
444
|
+
commits and digests, and `external:` pins (a stranger's repo is never "latest").
|
|
445
|
+
|
|
446
|
+
## Removed
|
|
447
|
+
|
|
448
|
+
Per-soul `source: git:…@v#…` lines and the `git:`/`repo:`/`path:` grammar;
|
|
449
|
+
`imports:` of member souls; `exports:` lists; `oats.yaml`; the
|
|
450
|
+
installed-capability tier (`.agents/capabilities/installed/`) and
|
|
451
|
+
`oats-config.yaml` entirely; `oats init` / `use` / `install` / `restore` /
|
|
452
|
+
`trust` / `list` / `catalog` / `remove` / `migrate` / `config` (each answers
|
|
453
|
+
`E_UNKNOWN_COMMAND` naming its replacement); per-soul `stores.<x>.inherit`;
|
|
454
|
+
ambient-skill exclusion at launch. Lock v1/v2 files are `E_LOCK_SCHEMA`.
|
|
455
|
+
There is no converter and no dual-schema reader: a 0.24.x kernel keeps
|
|
456
|
+
spawning 0.24.x deployments; see [rebuild-to-v2.md](rebuild-to-v2.md).
|
|
457
|
+
|
|
458
|
+
## Related
|
|
459
|
+
|
|
460
|
+
- [Souls and instances](souls-and-instances.md) · [Packages](packages.md) ·
|
|
461
|
+
[Configuration (`oats-local.yaml`)](configuration.md) · [Rebuild guide](rebuild-to-v2.md)
|
|
462
|
+
- [Capability manifests](capabilities.md) · [Contracts](layers.md) ·
|
|
463
|
+
[Desktop CLI API — workspace model](desktop-cli-api.md#workspace-model-workspaceapi-2)
|
|
464
|
+
- [Design navigation](design/README.md)
|