@awebai/oats 0.36.1 → 0.38.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/docs/desktop.md CHANGED
@@ -104,6 +104,39 @@ opened, or your home directory when there is none (never ~/Downloads);
104
104
 
105
105
  Launch flags for scripted use: `--dir <workspace>` and `OATS_DESKTOP_PORT`.
106
106
 
107
+ ### One window per workspace
108
+
109
+ Each workspace has at most one window, titled with the workspace's name (a
110
+ workspace on a server: `name — server`).
111
+
112
+ - **Switching.** Choosing a workspace in the switcher shows it in the current
113
+ window. If that workspace already has a window, that window comes to the
114
+ front instead and the current one doesn't change.
115
+ - **Open in new window.** Each workspace in the switcher has an **Open in new
116
+ window** button beside it. From the keyboard: Right Arrow on the workspace,
117
+ then Enter, or ⌘Enter on macOS / Ctrl+Enter on Linux and Windows. A
118
+ workspace that already has a window is brought to the front.
119
+ - **New Window.** On macOS, **File → New Window** (⌘⇧N); everywhere,
120
+ **Window: new window** in the command palette. The new window has no
121
+ workspace yet: it opens the switcher, and shows "Choose a workspace" until
122
+ you pick one. It reads nothing until then.
123
+ - **Moving between windows.** On macOS, ⌘\` cycles the app's windows, and the
124
+ **Window** menu lists them.
125
+ - **Restore.** Quitting and relaunching brings every window back with its
126
+ size and place (moved onto a visible display if its own is gone). A window
127
+ whose workspace isn't served at launch doesn't come back, but it is
128
+ remembered until you close it while its workspace is served. With nothing
129
+ to restore, one window opens on the last workspace you used.
130
+ - **Launching again.** Running the app again (`open -a "OATS Desktop" --args
131
+ --dir <deployment>`, or from inside a deployment) adds that deployment if
132
+ needed and brings its window to the front, opening one if it has none. Any
133
+ other launch brings the most recently used window to the front.
134
+ - **Closing.** Closing a window leaves the other windows, and their
135
+ terminals, running. Closing the last window quits the app.
136
+ - Tabs belong to their window: a workspace's terminals and tabs stay in the
137
+ window they were opened in, and switching that window back to the
138
+ workspace brings them back.
139
+
107
140
  ## One workspace, several machines
108
141
 
109
142
  The switcher lists each workspace once, however many deployments it has: the
@@ -102,17 +102,24 @@ that its session will stop at the folder-trust prompt, and so does
102
102
  With a messaging capability in the soul's composition, every instance lives in
103
103
  a team, and readiness fails with `E_TEAM_UNCONFIGURED` until there is a
104
104
  default. Create the team with your messaging provider (its own docs say how;
105
- for `oats.aweb`, see its skills), then record it here:
105
+ for `oats.aweb`, see its skills), then commit it in `oats-workspace.yaml` as a
106
+ **shared** team and the workspace's default:
107
+
108
+ ```yaml
109
+ teams:
110
+ research: { team: "<provider team id>" }
111
+ defaultTeam: research
112
+ ```
106
113
 
107
114
  ```bash
108
- oats teams add research --team <provider team id> # a local team; the first one becomes the default
109
- oats teams # shared and local teams, and the default
115
+ oats teams # shared and local teams, the default, the workspace's souls:
110
116
  ```
111
117
 
112
- A team the whole workspace uses is committed as a **shared** team in
113
- `oats-workspace.yaml`; which souls join which team on this machine is
114
- `oats soul teams` ([workspaces.md](workspaces.md#teams)). Without messaging,
115
- skip this step.
118
+ Which other teams each soul may join is committed there too, in `souls:`
119
+ ([workspaces.md](workspaces.md#teams)). A team only this deployment uses is a
120
+ **local** team (`oats teams add research --team <provider team id>`; the first
121
+ one becomes this deployment's default), which the workspace must allow with
122
+ `localTeams: true`. Without messaging, skip this step.
116
123
 
117
124
  ## 4. Look before you spawn
118
125
 
@@ -41,7 +41,7 @@ arrives from.
41
41
  # oats-workspace.yaml: one default per slot, for every soul
42
42
  packages:
43
43
  oats.okf: v4.1.1
44
- oats.aweb: v1.17.7
44
+ oats.aweb: v1.19.0
45
45
  oats.linear: v1.0.1
46
46
  oats.jira: v1.0.1
47
47
  defaults:
@@ -126,28 +126,16 @@
126
126
  "uniqueItems": true,
127
127
  "items": { "type": "string", "pattern": "^(?:[a-z0-9][a-z0-9._-]*/)?[a-z0-9]+(?:-[a-z0-9]+)*$" }
128
128
  },
129
- "teams": {
130
- "description": "Which teams each soul belongs to on this deployment (`oats soul teams` writes it): \"*\" applies to every soul; a soul's own entry (its bare name, or <package>/<soul> for a package soul) adds to it. Every soul is also in its default. Eligible, never auto-joined: an instance joins its default at spawn and the others on request.",
131
- "type": "object",
132
- "propertyNames": { "type": "string", "pattern": "^(?:\\*|(?:[a-z0-9][a-z0-9._-]*/)?[a-z0-9]+(?:-[a-z0-9]+)*)$" },
133
- "additionalProperties": { "type": "array", "uniqueItems": true, "items": { "$ref": "#/$defs/label" } }
134
- },
135
129
  "launch": {
136
130
  "description": "Launch preferences on this machine (0.30; feature launch-preference), overriding the soul's own launch: \"*\" applies to every soul; a soul's own entry (its bare name, or <package>/<soul>) wins over it. A value is a launch configuration's name (in launch-configs of this file) or an inline { harness, model? }. Explicit spawn flags win over both. A changed preference affects no existing home until --reselect-launch or a respawn.",
137
131
  "type": "object",
138
132
  "propertyNames": { "type": "string", "pattern": "^(?:\\*|(?:[a-z0-9][a-z0-9._-]*/)?[a-z0-9]+(?:-[a-z0-9]+)*)$" },
139
133
  "additionalProperties": { "oneOf": [ { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$" }, { "$ref": "#/$defs/launchPreference" } ] }
140
- },
141
- "default": {
142
- "description": "A per-soul override of defaultTeam (`oats soul teams <soul> --default <label>`); it must be one of that soul's teams here (E_TEAM_NOT_ELIGIBLE).",
143
- "type": "object",
144
- "propertyNames": { "type": "string", "pattern": "^(?:[a-z0-9][a-z0-9._-]*/)?[a-z0-9]+(?:-[a-z0-9]+)*$" },
145
- "additionalProperties": { "$ref": "#/$defs/label" }
146
134
  }
147
135
  }
148
136
  },
149
137
  "teams": {
150
- "description": "LOCAL teams (`oats teams add` writes it): <label>: { team, description? }, the messaging provider's team id for a team only this deployment uses. Shared teams are committed in oats-workspace.yaml teams:; a label in both is team-label-collision (the shared one wins).",
138
+ "description": "LOCAL teams (`oats teams add` writes it): <label>: { team, description? }, the messaging provider's team id for a team only this deployment uses. Allowed only when oats-workspace.yaml says localTeams: true (or in the standalone view); otherwise E_WORKSPACE_SCHEMA reason local-teams-closed. Every soul may join every local team. Shared teams are committed in oats-workspace.yaml teams:; a label in both is team-label-collision (the shared one wins). souls.teams and souls.default were removed in 0.38.0: souls: in oats-workspace.yaml.",
151
139
  "type": "object",
152
140
  "propertyNames": { "$ref": "#/$defs/label" },
153
141
  "additionalProperties": {
@@ -162,7 +150,7 @@
162
150
  },
163
151
  "defaultTeam": {
164
152
  "$ref": "#/$defs/label",
165
- "description": "The team every instance of this deployment lives in (its primary identity), unless souls.default overrides it for a soul: a label of teams here or in oats-workspace.yaml (`oats teams default` writes it; the first `oats teams add` sets it)."
153
+ "description": "This deployment's default team (`oats teams default` writes it; the first `oats teams add` sets it): a label of teams here or in oats-workspace.yaml. Allowed only when oats-workspace.yaml says localTeams: true (or in the standalone view). A soul's souls: default in the workspace wins over it; it wins over the workspace's defaultTeam."
166
154
  }
167
155
  },
168
156
  "$defs": {
@@ -25,21 +25,21 @@
25
25
  "type": "object",
26
26
  "propertyNames": { "$ref": "#/$defs/label" },
27
27
  "additionalProperties": { "$ref": "#/$defs/team" },
28
- "description": "SHARED teams: <label>: { description?, team? }. `team` is the messaging provider's team id, the same for everyone; without it the team is declared but not yet created (readiness team-unmapped). Local teams, the default and which teams each soul belongs to live in the deployment's oats-local.yaml (`oats teams`, `oats soul teams`). A label never gates, restricts or partitions anything."
28
+ "description": "SHARED teams: <label>: { description?, team? }. `team` is the messaging provider's team id, the same for everyone; without it the team is declared but not yet created (readiness team-unmapped). The default team and which teams each soul may join are defaultTeam and souls: below; a deployment's local teams live in its oats-local.yaml where localTeams allows them. A label never gates, restricts or partitions anything."
29
29
  },
30
30
  "defaultTeam": {
31
31
  "$ref": "#/$defs/label",
32
- "description": "Team model 3 (0.37.0): the workspace's fallback default team, a label of teams: in this file. 0.36.x accepts and validates it without applying it."
32
+ "description": "The workspace's fallback default team, a label of teams: in this file: a soul's souls: default wins over it, and so does a deployment's local defaultTeam where localTeams: true."
33
33
  },
34
34
  "localTeams": {
35
35
  "type": "boolean",
36
- "description": "Team model 3 (0.37.0): whether deployments may declare their own teams and defaultTeam in oats-local.yaml. Absent: false. 0.36.x accepts it without applying it."
36
+ "description": "Whether deployments may declare their own teams and defaultTeam in oats-local.yaml (every soul may join a local team). Absent: false; then local teams and a local defaultTeam are refused (E_WORKSPACE_SCHEMA reason local-teams-closed)."
37
37
  },
38
38
  "souls": {
39
39
  "type": "object",
40
40
  "propertyNames": { "type": "string", "pattern": "^(?:\\*|[a-z0-9][a-z0-9._-]*/(?:\\*|[a-z0-9]+(?:-[a-z0-9]+)*))$" },
41
41
  "additionalProperties": { "$ref": "#/$defs/soulTeams" },
42
- "description": "Team model 3 (0.37.0): per soul pattern (\"*\", <member|package>/* or <member|package>/<soul>; the most specific key wins outright), the soul's default team and the other teams it may join. Every label is a label of teams: in this file. 0.36.x accepts and validates it without applying it."
42
+ "description": "Per soul pattern (\"*\", <member|package>/* or <member|package>/<soul>), the soul's default team and the other teams it may join. The most specific key gives the soul's teams outright (lists never merge); the most specific key that sets a default gives its default. A soul no key matches has its default only. Every label is a label of teams: in this file."
43
43
  },
44
44
  "defaults": { "$ref": "#/$defs/defaults" },
45
45
  "stores": {
@@ -9,9 +9,9 @@ or workspace membership alone does not make a package official.
9
9
 
10
10
  | package | release | capabilities | package souls |
11
11
  |---|---|---|---|
12
- | `oats.framework` | `oats-framework/v1.4.2` (this repository) | `oats.core`, `oats.setup`, `oats.knowledge-theory` | `knowledge-theory-expert` |
12
+ | `oats.framework` | `oats-framework/v1.5.0` (this repository) | `oats.core`, `oats.setup`, `oats.knowledge-theory` | `knowledge-theory-expert` |
13
13
  | `oats.okf` | `v4.1.1` | `oats.okf` (knowledge), `oats.okf-harvest`, `oats.okf-maintenance` | `knowledge-harvester`, `knowledge-maintainer` |
14
- | `oats.aweb` | `v1.17.7` | `oats.aweb` (messaging) | |
14
+ | `oats.aweb` | `v1.19.0` | `oats.aweb` (messaging) | |
15
15
  | `oats.engineering` | `v1.5.0` | `oats.engineering-expert`, `oats.developer`, `oats.code-review` | `code-reviewer` |
16
16
  | `oats.authoring` | `v1.0.3` | `oats.authoring` | |
17
17
  | `oats.jira` | `v1.0.1` | `oats.jira` (tasks) | |
package/docs/packages.md CHANGED
@@ -51,7 +51,7 @@ packages:
51
51
  - **Bare version** (`v4.1.1`, `4.1.1`, `1.0.0-rc.1`): the id is looked up in
52
52
  the official catalog — `package-catalog.json` in the `oats` repo, or the file
53
53
  named by `OATS_PACKAGE_CATALOG` — which supplies the repo url, the tag
54
- convention (`v4.1.1` or `oats-framework/v1.4.2`) and the payload path. An id
54
+ convention (`v4.1.1` or `oats-framework/v1.5.0`) and the payload path. An id
55
55
  the catalog does not know is `E_PACKAGE_MISSING` ("use `git:<repo>@<ref>` for
56
56
  a package outside the catalog"). The catalog is the reviewed official list
57
57
  ([official-catalog.md](official-catalog.md)) and the only way a
@@ -74,9 +74,9 @@ members:
74
74
  - git:github.com/acme/agents
75
75
  - git:github.com/acme/platform
76
76
  packages:
77
- oats.framework: v1.4.2
77
+ oats.framework: v1.5.0
78
78
  oats.okf: v4.1.1
79
- oats.aweb: v1.17.7
79
+ oats.aweb: v1.19.0
80
80
  teams:
81
81
  platform: { team: "platform:acme.aweb.ai", description: Platform engineering }
82
82
  defaults:
@@ -134,7 +134,7 @@ Declaring a package in the workspace's `packages:` is the trust decision
134
134
  ## `oats package add | remove`
135
135
 
136
136
  ```bash
137
- oats package add oats.aweb v1.17.7 # a catalog version
137
+ oats package add oats.aweb v1.19.0 # a catalog version
138
138
  oats package add acme.tools git:github.com/acme/tools@v0.4.0
139
139
  oats package remove acme.tools
140
140
  ```
@@ -247,8 +247,9 @@ oats-package/
247
247
  souls; otherwise `E_SOUL_AMBIGUOUS` names each qualified form
248
248
  (`details.qualified`). A member soul's qualified form is `<member name>/<soul>`.
249
249
  - **Resolved** like any soul: the workspace defaults apply, `off` and
250
- `<slot>: none` work, its teams here are keyed `<package>/<soul>` in
251
- `oats-local.yaml` `souls.teams`, and `from: here` means **this package** at the locked commit
250
+ `<slot>: none` work, its teams are keyed `<package>/<soul>` (or `<package>/*`)
251
+ in the workspace's `souls:` (unlisted, it joins its default only), and
252
+ `from: here` means **this package** at the locked commit
252
253
  (a capability it does not provide is `E_CAPABILITY_MISSING`).
253
254
  - **Spawned** at the locked commit: the soul is fetched into the per-commit
254
255
  soul cache and its digest must equal the lock's (`E_PACKAGE_INTEGRITY
@@ -332,11 +333,11 @@ A soul that names one of the package's capabilities with
332
333
  "policy": "docs/official-catalog.md",
333
334
  "packages": {
334
335
  "oats.okf": { "url": "https://github.com/awebai/oats-okf.git", "ref": "v4.1.1", "path": "oats-package" },
335
- "oats.framework": { "url": "https://github.com/awebai/oats.git", "ref": "oats-framework/v1.4.2", "path": "oats-package" }
336
+ "oats.framework": { "url": "https://github.com/awebai/oats.git", "ref": "oats-framework/v1.5.0", "path": "oats-package" }
336
337
  }
337
338
  }
338
339
  ```
339
340
 
340
- `ref` carries the tag convention: a workspace's `oats.framework: v1.4.2`
341
- resolves to tag `oats-framework/v1.4.2`. Resolving through the catalog never
341
+ `ref` carries the tag convention: a workspace's `oats.framework: v1.5.0`
342
+ resolves to tag `oats-framework/v1.5.0`. Resolving through the catalog never
342
343
  advances a lock by itself: `oats sync` does, and says so.
@@ -0,0 +1,111 @@
1
+ # OATS 0.37.0
2
+
3
+ ## Added
4
+
5
+ - **The Desktop opens one window per workspace** (awebai/oats#481). Each
6
+ workspace has at most one window, titled with its name (a workspace on a
7
+ server: `name — server`). Choosing a workspace in the switcher shows it in the
8
+ current window, or brings its own window to the front when it has one.
9
+ Each workspace in the switcher has an **Open in new window** button
10
+ (keyboard: Right Arrow then Enter, or ⌘Enter / Ctrl+Enter). On macOS,
11
+ **File → New Window** (⌘⇧N) opens a window with no workspace that asks you
12
+ to choose one, and ⌘\` cycles the windows; on Linux and Windows the command
13
+ palette has **Window: new window**, with no default chord. Quitting and
14
+ relaunching brings every window back with its size and place; a window
15
+ whose workspace isn't served at launch stays remembered until you close it
16
+ while it is served. Launching the app again with `--dir <deployment>`
17
+ brings that deployment's window to the front, adding the deployment if
18
+ needed. An unfocused window re-reads its instance list at the server's
19
+ blurred cadence (30 s). A window's requests always carry its own
20
+ workspace: one the server doesn't serve is refused with
21
+ `E_WORKSPACE_NOT_SERVED`, never answered with another workspace's data.
22
+ Window positions are kept in `windows.json` in the app's data folder.
23
+
24
+ ## Changed
25
+
26
+ - **The Desktop accepts OATS CLIs `>=0.25.8 <0.38.0`**, so it runs against
27
+ this release's kernel. Install the CLI and the Desktop 0.37.0 together: the
28
+ Desktop 0.36.x refuses a 0.37 CLI.
29
+
30
+ - **Team model 3 is OATS 0.38.0, not 0.37.0.** 0.37.0 keeps 0.36.1's
31
+ behaviour: the new `oats-workspace.yaml` keys are validated but not applied,
32
+ and the `team-model-3-migration` warning, which now names 0.38.0, says what
33
+ 0.38.0 will refuse. The migration steps in the 0.36.1 notes apply unchanged.
34
+
35
+ - **oats.framework 1.4.3** (oats.setup 2.2.2, catalog and workspace pin
36
+ `oats-framework/v1.4.3`): the `oats-teams` and
37
+ `oats-workspace-config` skills say team model 3 is 0.38.0, and that 0.36.x
38
+ and 0.37.x keep applying the local team keys. Remove them only when the
39
+ deployment moves to 0.38.0.
40
+
41
+ - **oats.aweb 1.18.1** (catalog and workspace pin, and the bundled mirror):
42
+ how mail reaches a session now depends on the runtime. Under the default
43
+ `delivery: channel`, Claude Code takes mail and chat through its
44
+ `aweb-channel` plugin and pi through the `@awebai/pi` extension, and
45
+ neither goes through the host wake broker. Codex keeps the wake broker. A
46
+ Codex home under `channel` is now registered with it at every start, where
47
+ before it got no wake at all. Every start of a home re-decides the path for
48
+ that start's runtime and leaves the home on exactly one path. A start that
49
+ cannot register or deregister the home is refused. Existing instances keep
50
+ the delivery they were spawned with until they are respawned.
51
+ `delivery: session` still sends every runtime through the broker.
52
+
53
+ oats.aweb 1.18.1 declares `launchPreview` (see Fixed below). A launch
54
+ preview, including Desktop's start dialog, and a start refused by
55
+ preflight change no broker registration: the hook calls `aw` only in the
56
+ real run, after preflight has passed.
57
+
58
+ ## Fixed
59
+
60
+ - **Launch hooks no longer have side effects under a preview, or before a
61
+ start's preflight has passed** (awebai/oats#500). A launch hook may register
62
+ a home with its provider; oats.aweb's registers it with the host wake
63
+ broker. But `oats launch-config preview`, which Desktop's start dialog
64
+ calls for running homes, ran every hook for real. A start refused by
65
+ preflight, including a start of a home that was already running, had also
66
+ run them. Previewing a running Claude home with another harness could
67
+ therefore change how that live session is woken.
68
+
69
+ A capability now declares that its launch hook is preview-aware, with
70
+ `"launchPreview": true` at the top level of its manifest. The kernel reads
71
+ the declaration from the home's own module copy. A preview-aware hook
72
+ runs twice per start:
73
+ - **As a preview,** with `OATS_LAUNCH_PREVIEW=1` in the hook environment
74
+ (a new, additive hook environment variable). This run's contribution
75
+ drives the preflight and the rendered command.
76
+ - **For real, without the flag,** only once every check has passed, and
77
+ before a restart stops the running harness.
78
+
79
+ A preview runs only the first pass. A preview-aware hook must change
80
+ nothing under the flag. It must also return the same contribution either
81
+ way, apart from the values of the env names its preview answer lists in
82
+ `volatileEnv` (below). If the real run's contribution differs otherwise,
83
+ the start is refused with `E_LAUNCH_PREPARATION` and nothing is stopped or
84
+ started.
85
+
86
+ A hook that doesn't declare `launchPreview` runs as it did in 0.36: once
87
+ per start, for real, during preflight. `oats launch-config preview` never
88
+ runs it; the preview shows its recorded contribution and says so. The
89
+ kernel never passes on an `OATS_LAUNCH_PREVIEW` inherited from the
90
+ environment.
91
+
92
+ **Provider authors:** declare `launchPreview` once your launch hook
93
+ honours `OATS_LAUNCH_PREVIEW`, and expect it to run twice per start
94
+ (docs/capabilities.md). oats.aweb 1.18.1 does. A kernel before 0.37
95
+ ignores the key and runs the hook once, as before.
96
+ - **Existing homes whose launch hook renews a credential at every start
97
+ start again** (awebai/oats#504). The first version of the two-pass rule
98
+ above ran every launch hook twice. A hook from before the preview flag
99
+ then really ran under the preview too, and a hook that answers new values
100
+ at every call failed the comparison. That covers oats.aweb homes with
101
+ `identity.mode: global` and `renew: launch`, which mint a grant at each
102
+ start: their starts were refused with `E_LAUNCH_PREPARATION`. Two passes
103
+ are now opt-in, through `launchPreview`.
104
+
105
+ A preview-aware hook can also name, in its preview answer, env whose
106
+ values only the real run can know, such as `"volatileEnv":
107
+ ["AWEB_IDENTITY_HOME"]`. The start takes those values from the real run
108
+ and leaves them out of the comparison. Each must be a name the same hook
109
+ returned. A harness configuration selector (`CLAUDE_CONFIG_DIR`,
110
+ `CODEX_HOME`, `PI_CODING_AGENT_DIR`) is refused as volatile, because the
111
+ package probe reads it before the real run.
@@ -0,0 +1,128 @@
1
+ # OATS 0.38.0
2
+
3
+ ## Changed (breaking)
4
+
5
+ - **The Desktop accepts OATS CLIs `>=0.25.8 <0.39.0`**, so it runs against
6
+ this release's kernel. Install the CLI and the Desktop 0.38.0 together: the
7
+ Desktop 0.37.x refuses a 0.38 CLI.
8
+
9
+ - **Team model 3: the teams a soul may join, and its default, are committed
10
+ in the workspace** (awebai/oats#484; feature `team-model-3` replaces
11
+ `team-model-2`). An instance in two teams is a bridge between them, so which
12
+ teams an organisation's instances may be in is now its decision, committed
13
+ in `oats-workspace.yaml` and closed by default. This prevents accidental
14
+ joins and makes the intended team set reviewable in git; it does not stop a
15
+ deliberate relay by someone holding credentials for two teams (see
16
+ docs/design/2026-10-02-team-model-3.md).
17
+ - `oats-workspace.yaml` `defaultTeam`, `localTeams` and `souls:` (accepted
18
+ since 0.36.1) now apply. `souls:` keys are `"*"`, `<member|package>/*` and
19
+ `<member|package>/<soul>`; the most specific key gives a soul's teams
20
+ outright (lists never merge), and the most specific key that sets one
21
+ gives its default: `a/*: {default: security}` and `a/x: {teams: [docs]}`
22
+ give `a/x` the default `security` and the teams `[docs]` only. `teams:
23
+ any` is every shared team. A soul no key matches has its default only,
24
+ package souls included.
25
+ - A soul's default team is its `souls:` default; else the deployment's
26
+ `oats-local.yaml` `defaultTeam`, only when the workspace says
27
+ `localTeams: true`; else the workspace's `defaultTeam`; else none.
28
+ `defaultTeam.from` and `OATS_DEFAULT_TEAM_FROM` are `soul`, `deployment`
29
+ or `workspace`; `soul` now means the workspace's `souls:` default (it was
30
+ the local `souls.default`).
31
+ - A soul may join its default, its `souls:` teams and, with
32
+ `localTeams: true`, every local team. Every team row carries `via`
33
+ (`default`, `workspace`, `local`), in reports, `OATS_TEAMS` and
34
+ `instance.json`.
35
+ - `oats-local.yaml` `souls.teams` and `souls.default` are refused
36
+ (`E_WORKSPACE_SCHEMA`, reason `removed-key`). The refusal names each key
37
+ and prints the `souls:` to commit instead (`details.replacement`); `oats
38
+ doctor` answers with it.
39
+ - `oats-local.yaml` `teams` and `defaultTeam` are refused unless the
40
+ workspace says `localTeams: true` (`E_WORKSPACE_SCHEMA`, reason
41
+ `local-teams-closed`, naming both fixes). Spawn, preview and inspect
42
+ refuse; `oats teams`, readiness and `oats doctor` (offline, from the cached
43
+ workspace file) report it as a failure. The standalone view allows them.
44
+ - `oats teams add` and `oats teams default` are refused where local teams
45
+ are closed; `oats teams remove` still works there. `oats soul teams` is
46
+ read only: `--add`, `--remove`, `--default` and `--clear-default` are
47
+ `E_BAD_ARGS`, naming `souls:` in `oats-workspace.yaml`.
48
+ - `oats teams --json` is `teamsApi: 2` (`localTeams`, a `DefaultTeam`, the
49
+ workspace's `souls:`) and now discovers the workspace's souls; `oats soul
50
+ teams --json` is `soulTeamsApi: 2` (`key` is the qualified soul key,
51
+ `match` and `defaultMatch` name the `souls:` keys that applied).
52
+ - New readiness items: local-teams-closed (failure) and `team-soul-unknown`
53
+ (a warning for a `souls:` key that names no soul of the workspace).
54
+ `team-model-3-migration` is retired, and the kernel no longer reports
55
+ `E_TEAM_NOT_ELIGIBLE` (a soul's default is always eligible).
56
+ - Shapes: docs/desktop-cli-api.md, under Teams.
57
+ - **The Desktop follows team model 3.** With a 0.38 CLI (feature `team-model-3`) the Workspace ›
58
+ Teams page shows the workspace's `souls:` read only, and offers Add and Make default for local teams
59
+ only where the workspace says `localTeams: true`; where it doesn't, a local team still in
60
+ `oats-local.yaml` keeps Remove under the kernel's local-teams-closed message. A soul's *Teams here* is
61
+ read only: it says where the soul's default and teams come from and that they change as `souls:` in
62
+ `oats-workspace.yaml` (a PR to the workspace file). The Desktop never sends the removed `oats soul
63
+ teams` edit flags, and a 0.37 or older CLI keeps the team model v2 views.
64
+ - **oats.aweb 1.19.0** (catalog and workspace pin, and the bundled mirror),
65
+ for team model 3. A default team that comes from the workspace's
66
+ `defaultTeam` is reported as `from: "workspace"` in `oats aweb teams --json`
67
+ and the recorded meta (1.18 reported it as `deployment`). `oats aweb setup
68
+ --create` asks the kernel whether the workspace allows local teams before it
69
+ creates anything; where it does not, it creates the team and its per-team
70
+ root, records no local team, and prints the `teams:` entry (and
71
+ `defaultTeam:`) to commit in `oats-workspace.yaml`, or the `localTeams: true`
72
+ alternative. `--username` and setup's other advice say the same where local
73
+ teams are closed, and an unmapped default's remedy names `defaultTeam:` in
74
+ the workspace file. Joining stays limited to the eligible `OATS_TEAMS` rows.
75
+
76
+ - **oats.framework 1.5.0** (oats.core 2.3.0, oats.setup 2.3.0; catalog and
77
+ workspace pin `oats-framework/v1.5.0`): the skills
78
+ describe team model 3. `oats-teams`, `oats-workspace-config`,
79
+ `oats-setup-model`, `oats-onboarding` and the setup inject say a soul's teams
80
+ and default are committed in `souls:` of `oats-workspace.yaml`, local teams
81
+ only with `localTeams: true`; `oats-operate` and `oats-souls` say where an
82
+ instance's teams come from.
83
+
84
+ ## Upgrade
85
+
86
+ Before upgrading a deployment, its workspace must say what its
87
+ `oats-local.yaml` said. 0.36.1 and 0.37.x already accept the workspace keys,
88
+ and their `team-model-3-migration` warnings list what each deployment has to
89
+ move.
90
+
91
+ 1. **In the workspace file** (a PR, once everyone who reads it runs 0.36.1 or
92
+ later):
93
+ - commit `souls:` entries for what the deployments' `souls.teams` and
94
+ `souls.default` said. A v2 soul's teams were its default, `"*"`'s teams
95
+ and its own, and patterns never merge, so a soul's own entry lists
96
+ `"*"`'s teams too. Qualify bare soul names with their member's name
97
+ (`<member>/<soul>`, as `souls.disabled` names it). Running 0.38.0 against
98
+ an unmigrated `oats-local.yaml` prints this snippet for you;
99
+ - commit `defaultTeam: <label>` when the deployments' default is a shared
100
+ team;
101
+ - for local (personal) teams and a local default, choose: (a) keep them
102
+ personal, with `localTeams: true`; or (b) commit them in `teams:` (and the
103
+ default as `defaultTeam:`).
104
+ 2. **In each `oats-local.yaml`**, when the deployment moves to 0.38.0: remove
105
+ `souls.teams` and `souls.default`, and, under (b), the local `teams` and
106
+ `defaultTeam` (`oats teams remove` works while local teams are closed).
107
+ 3. **Check:** `oats teams` lists no problems, and `oats spawn <soul>
108
+ --preview` shows each soul's `defaultTeam` and `teams` as intended. Running
109
+ instances keep their spawn-time default; readiness reports
110
+ `default-team-changed` where it moved, and a respawn follows it.
111
+
112
+ What each kind of deployment changes:
113
+
114
+ - **A workspace whose people keep personal teams or a personal default** (for
115
+ example awebai/oats): `localTeams: true`, plus `souls:` for the souls'
116
+ shared teams (e.g. `"*": { teams: [oats] }`); drop `souls.teams` locally.
117
+ - **A workspace whose default team is a committed shared team**: commit
118
+ `defaultTeam: <label>` and the `souls:` entries; drop the local
119
+ `defaultTeam` (and any `souls.*` team keys).
120
+ - **A standalone deployment**: nothing changes for local teams and the local
121
+ default; drop `souls.teams` and `souls.default` (a standalone view has no
122
+ `souls:` patterns: every soul may join every local team).
123
+
124
+ Consumers: the Desktop's team views read `teamsApi: 1` / `soulTeamsApi: 1` and
125
+ edit through `oats soul teams --add`; they follow in their own change. Another
126
+ messaging provider receives `OATS_DEFAULT_TEAM_FROM=workspace` and `via` on
127
+ the `OATS_TEAMS` rows; one that records local teams with `oats teams add` needs
128
+ the workspace to allow them (oats.aweb 1.19.0 does both, above).
@@ -164,8 +164,8 @@ composed skills and instructions, a spawn records:
164
164
  "key": "github.com/acme/agents", "commit": "3f2a9c1e…", "resolution": "20ec8ec527311d71d0973086",
165
165
  "soul": { "repoKey": "github.com/acme/agents", "commit": "3f2a9c1e…" }
166
166
  },
167
- "teams": [{ "label": "ana-acme", "team": "ana-acme:ana.aweb.ai", "default": true, "from": "local" },
168
- { "label": "engineering", "team": "engineering:acme.aweb.ai", "default": false, "from": "shared" }],
167
+ "teams": [{ "label": "ana-acme", "team": "ana-acme:ana.aweb.ai", "default": true, "from": "local", "via": ["default", "local"] },
168
+ { "label": "engineering", "team": "engineering:acme.aweb.ai", "default": false, "from": "shared", "via": ["workspace"] }],
169
169
  "defaultTeam": { "label": "ana-acme", "team": "ana-acme:ana.aweb.ai", "from": "deployment" }
170
170
  }
171
171
  ```
@@ -183,7 +183,8 @@ composed skills and instructions, a spawn records:
183
183
  compares `workspace.soul` with the member's current commit too: `soul: <name>
184
184
  from <member> @ <c7> [member moved since …]` (`--json`: `instances[].soul`).
185
185
  - `teams` / `defaultTeam`: the soul's teams at spawn, exactly as the providers
186
- received them (mapped teams only) and its default: evidence, never rewritten.
186
+ received them (mapped teams only, each with `via`: why the soul may join it)
187
+ and its default: evidence, never rewritten.
187
188
  A running home's hooks and messaging commands read the teams live
188
189
  ([capabilities.md](capabilities.md#teams-in-the-provider-environment)).
189
190