@awebai/oats 0.37.0 → 0.38.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -37,7 +37,7 @@ canonical (`github.com/<org>/<repo>`, or `local/<abs-path>`). Examples use
37
37
  "instance-git-remote","souls-declarations","lifecycle-plans","retire-retention","readiness","spawn-preview","instance-events",
38
38
  "instance-events-2","schedule-history","schedule-read-2","spawn-preview-2","spawn-idempotency","spawn-idempotency-2","spawn-apply-2",
39
39
  "workspace-v2","instance-modules","spawn-provider-payload","served-identity","packages-no-approval","spawn-name","settings-origins",
40
- "team-model-2","settings-declared","capabilities-private","layers-from","harness","package-souls","triggers","automations","desktop-facts","launch-preference",
40
+ "team-model-3","settings-declared","capabilities-private","layers-from","harness","package-souls","triggers","automations","desktop-facts","launch-preference",
41
41
  "preview-composed-from","observe-max-age","spawn-preview-max-age","capability-show","capture-file","workspace-identity"],
42
42
  "automationsApi":1,"workspaceApi":2,"instanceGitApi":1,"spawnApplyApi":1,"soulsApi":2,"lifecycleApi":1,
43
43
  "readinessApi":2,"spawnPreviewApi":2,"eventsApi":2,"scheduleHistoryApi":3,"scheduleApi":2,"operationsApi":2,
@@ -88,7 +88,7 @@ canonical (`github.com/<org>/<repo>`, or `local/<abs-path>`). Examples use
88
88
  | `settings-declared` | `declares` on inspect capabilities and preview modules | |
89
89
  | `capabilities-private` | `private` on `oats capabilities` rows | |
90
90
  | `layers-from` | `layers.<slot>.from` in inspect | |
91
- | `team-model-2` | every team field; `oats teams`, `oats soul teams` | `teamsApi: 1`, `soulTeamsApi: 1` (payload only) |
91
+ | `team-model-3` | every team field; `oats teams`, `oats soul teams` (0.38.0; replaces `team-model-2`) | `teamsApi: 2`, `soulTeamsApi: 2` (payload only) |
92
92
  | `harness` | the harness names ([Harness input spellings](#the-harness-rename-feature-harness-oats-0270)) | |
93
93
  | `package-souls` | package soul rows, `qualifiedName`, `packages[].souls` | |
94
94
  | `triggers` | `oats trigger …` | `triggerApi: 1` (payload only) |
@@ -253,9 +253,11 @@ oats capabilities show <name> … --max-age <seconds> --json (feature capabilit
253
253
  commit can no longer be fetched. Each is observed live, as without the flag.
254
254
  A live observation that fails is the usual error, never an older head.
255
255
  - **Refusals:** every other command, a spawn apply (with or without
256
- `--expect-decision`; the form named is `spawn`), every edit form (`teams add|remove|default`,
257
- `soul teams --add|--remove|--default|--clear-default`) and any `--server`
258
- invocation refuse the flag before reading or writing anything, with
256
+ `--expect-decision`; the form named is `spawn`), every edit form (`teams add|remove|default`)
257
+ and any `--server`
258
+ invocation refuse the flag before reading or writing anything (the removed
259
+ `soul teams --add|--remove|--default|--clear-default` answer their own
260
+ `E_BAD_ARGS` first), with
259
261
  `E_BAD_ARGS` "--max-age is not accepted by \`oats <form>\`: only the read
260
262
  verbs reuse observations (status, workspace status, souls, capabilities,
261
263
  capabilities show, inspect --soul|--home, spawn --preview, and the read
@@ -397,7 +399,8 @@ message}`.
397
399
  A soul whose resolution is refused is an inspect error with the resolver's
398
400
  code and details (`E_PACKAGE_MISSING`, `E_PACKAGE_INTEGRITY`,
399
401
  `E_CAPABILITY_MISSING`, `E_LOCK_SCHEMA`, `E_REQUIREMENT_INACTIVE`,
400
- `E_TEAM_UNKNOWN`, `E_TEAM_NOT_ELIGIBLE`); readiness reports the same condition
402
+ `E_TEAM_UNKNOWN`, and `E_WORKSPACE_SCHEMA` reason `local-teams-closed`);
403
+ readiness reports the same condition
401
404
  as an item. Soul lookup errors are those of [spawn](#spawn-errors).
402
405
 
403
406
  <a id="oats-readiness---home-----soul----dir----policy---json--readinessapi-2"></a>
@@ -595,6 +598,7 @@ A removed verb answers, before any namespace can claim it:
595
598
  | `status --team` | `E_BAD_ARGS` | `oats status` in the deployment |
596
599
  | `spawn --instance` | `E_BAD_ARGS` | `--purpose` or `--name` |
597
600
  | `spawn --ephemeral`, `--instructions-file`, `--def-file` | `E_BAD_ARGS` | a soul in a member repository |
601
+ | `soul teams --add`, `--remove`, `--default`, `--clear-default` (0.38.0) | `E_BAD_ARGS` (`details: {flag, replacement}`) | `souls:` in `oats-workspace.yaml` |
598
602
  | `session … --native-record` | `E_BAD_ARGS` | none |
599
603
 
600
604
  `details.replacement` is the kernel's prose (shortened above): show it, do not
@@ -750,7 +754,7 @@ Read-only (it writes no lock):
750
754
  - `declaredPackages`: the ids in `packages:` (standalone: the kernel's
751
755
  default). `unsynced`: declared, not locked. `stale`: locked, no longer
752
756
  declared. `external[]`: `{source, soul}`.
753
- - `workspace.teams` (feature `team-model-2`): the shared teams `{label, team,
757
+ - `workspace.teams` (feature `team-model-3`): the shared teams `{label, team,
754
758
  description}` by label; `[]` standalone.
755
759
  - `problems`, `warnings`: as in sync.
756
760
  - `automations` (feature `automations`): `{host, snapshot: {takenAt,
@@ -788,7 +792,7 @@ packages' capabilities and souls, sorted by name, then origin. Both carry
788
792
  "version":"4.1.1","repoKey":"github.com/awebai/oats-okf","commit":"e1d604f7…","teams":null,"defaultTeam":null,"private":false,
789
793
  "path":"oats-package/souls/knowledge-maintainer","work":"directory","description":"Reviews harvested knowledge.","harness":"pi","model":null,
790
794
  "harnessFrom":"kernel-default","file":{"path":"oats-package/souls/knowledge-maintainer/soul.yaml","url":null},
791
- "spawnable":false,"problem":{"code":"E_TEAM_UNKNOWN","message":"team \"reviewers\" is not declared (oats-local.yaml#/souls/teams/…)"}}],
795
+ "spawnable":false,"problem":{"code":"E_TEAM_UNKNOWN","message":"team \"reviewers\" is not declared (oats-local.yaml#/defaultTeam): …"}}],
792
796
  "problems":[]}
793
797
  ```
794
798
 
@@ -804,13 +808,13 @@ are absent until `sync`.
804
808
  commit, teams, defaultTeam, private (always false), path, work, description`,
805
809
  plus the Desktop facts `harness, model, harnessFrom, file, spawnable,
806
810
  problem`, and (feature `launch-preference`) `key` and `launch`.
807
- - `key` is the soul key that `souls.teams`, `souls.default` and `souls.launch`
808
- use, and that `oats soul teams <key>` takes: `qualifiedName` for a package
811
+ - `key` is the soul key that `souls.launch` uses, and that
812
+ `oats soul teams <key>` takes: `qualifiedName` for a package
809
813
  soul, the bare `name` for a member or external soul. Two member souls that
810
814
  share a bare name share one entry (spawning that name is
811
815
  `E_SOUL_AMBIGUOUS`).
812
816
  - `launch` is a [Launch](#the-launch-report-launch).
813
- - `teams` and `defaultTeam` (feature `team-model-2`) are a
817
+ - `teams` and `defaultTeam` (feature `team-model-3`) are a
814
818
  [TeamRow](#the-team-row-teamrow) list and a
815
819
  [DefaultTeam](#the-default-defaultteam); both `null` when the soul's teams
816
820
  do not resolve (`problem` names the `E_TEAM_*` code).
@@ -1012,7 +1016,7 @@ them. Gate each field below on it.
1012
1016
  v2 soul.yaml cannot). `spawnable`/`problem`: whether a spawn here would
1013
1017
  refuse, resolved without spawning, writing or reaching past the sync cache;
1014
1018
  `problem` is `{code, message}` or `null` (`E_SOUL_DISABLED`,
1015
- `E_TEAM_UNKNOWN`, `E_TEAM_NOT_ELIGIBLE`, `E_CAPABILITY_*`, `E_PACKAGE_*`,
1019
+ `E_TEAM_UNKNOWN`, `E_WORKSPACE_SCHEMA` (local-teams-closed), `E_CAPABILITY_*`, `E_PACKAGE_*`,
1016
1020
  `E_LOCK_SCHEMA`, `E_REMOTE_*`, …). `file`: `{path, url}` of soul.yaml.
1017
1021
  - **Capabilities.** `layer` on every row, `null` outside the slots.
1018
1022
  `description` or `null`. `skills`, `commands`, `hooks`: names, sorted
@@ -1039,61 +1043,91 @@ them. Gate each field below on it.
1039
1043
  `…/tree/<commit>`); any other host gives `url: null` with `path` set.
1040
1044
  `path` is repository-relative.
1041
1045
 
1046
+ <a id="team-model-3-feature-team-model-3-oats-0370-replaces-feature-team-model-2"></a>
1042
1047
  <a id="team-model-v2-feature-team-model-2-oats-0300-replaces-feature-teams"></a>
1043
1048
  ## Teams
1044
1049
 
1045
- Feature `team-model-2`: gate every team field and verb on it. Design:
1046
- [team model v2](design/2026-09-27-team-model-v2.md); operator guide:
1047
- [workspaces.md](workspaces.md).
1048
-
1049
- - **Shared teams** live in the committed `oats-workspace.yaml`:
1050
- `teams.<label> = {description?, team?}`. A shared team without `team` (the
1051
- provider id) is **unmapped**.
1052
- - **Local** configuration lives in `oats-local.yaml`: `teams.<label> = {team,
1053
- description?}`, `defaultTeam: <label>`, `souls.teams: {"*": [labels],
1054
- "<soul>": [labels]}` and `souls.default: {"<soul>": <label>}`. A soul key is
1055
- the spawn name (`<package>/<soul>` for a package soul). A label matches
1056
- `[a-z0-9][a-z0-9._-]*`.
1050
+ Feature `team-model-3` (OATS 0.38.0; it replaces `team-model-2`): gate every
1051
+ team field and verb on it. Design: [team model 3](design/2026-10-02-team-model-3.md);
1052
+ operator guide: [workspaces.md](workspaces.md#teams).
1053
+
1054
+ - **The workspace file** (`oats-workspace.yaml`, committed) holds the shared
1055
+ teams `teams.<label> = {description?, team?}` (a shared team without `team`,
1056
+ the provider id, is **unmapped**), `defaultTeam: <label>` (the workspace's
1057
+ fallback default), `localTeams: true|false` (absent: false) and `souls:`
1058
+ (per pattern, `{default?, teams?: [labels] | "any"}`). Every label there is
1059
+ a shared label of that file; anything else is `E_WORKSPACE_SCHEMA` when the
1060
+ file is read.
1061
+ - **`oats-local.yaml`** may declare local teams `teams.<label> = {team,
1062
+ description?}` and `defaultTeam: <label>` only where the workspace says
1063
+ `localTeams: true`, or in the standalone view (no workspace file is read).
1064
+ Elsewhere they are refused: `E_WORKSPACE_SCHEMA` with details `{reason:
1065
+ "local-teams-closed", path: "oats-local.yaml", keys}`, the message naming
1066
+ both fixes. `souls.teams` and `souls.default` are removed keys
1067
+ (`E_WORKSPACE_SCHEMA`, reason `removed-key`): the refusal names each key
1068
+ found, and `details.replacement` is the `souls:` YAML to commit instead (a
1069
+ bare soul name written `<member>/<soul>` for the operator to qualify), also
1070
+ printed at the end of the message. Every command that reads the file
1071
+ refuses, an instance home's messaging commands, operations, `inspect --home`
1072
+ and `readiness --home` included: they never fall back to the home's
1073
+ recorded teams for it. A home's other capability commands read only the
1074
+ home's own record, as always.
1075
+ - **A soul's key** is its qualified name: `<package>/<soul>`, or
1076
+ `<member>/<soul>` with the member repository's name (as `souls.disabled`
1077
+ names it). A label matches `[a-z0-9][a-z0-9._-]*`.
1057
1078
  - **Team ids.** A `team` value (in either file, and `oats teams add --team`)
1058
1079
  matches `^[A-Za-z0-9][A-Za-z0-9._:@/+-]{0,255}$`: the kernel's safety rule
1059
1080
  (never `-`-led, no whitespace or control characters, bounded). Otherwise
1060
1081
  `E_WORKSPACE_SCHEMA` (a file) or `E_BAD_ARGS` (the verb). The messaging
1061
1082
  provider validates its own id shape (oats.aweb: `<name>:<namespace>`).
1062
- - **Resolution.** The soul's default is `souls.default[soul] ??
1063
- defaultTeam`; its teams are that default plus `souls.teams["*"]` plus
1064
- `souls.teams[soul]`. A label in both files is a `team-label-collision`
1065
- warning, and the shared definition wins. An undeclared label is
1066
- `E_TEAM_UNKNOWN`; a `souls.default` outside the soul's teams is
1067
- `E_TEAM_NOT_ELIGIBLE`.
1083
+ - **Resolution.** The `souls:` patterns for a soul are its key, then
1084
+ `<member|package>/*`, then `"*"`. The most specific one that exists gives
1085
+ the soul's teams outright (lists never merge); the most specific one that
1086
+ sets `default` gives its default. So `a/*: {default: security}` and
1087
+ `a/x: {teams: [docs]}` give `a/x` the default `security` and the teams
1088
+ `[docs]` only. The default is that `default`; else the local `defaultTeam`
1089
+ where local teams are allowed; else the workspace's `defaultTeam`; else
1090
+ none. A soul's teams are its default, its pattern's `teams` (`any`: every
1091
+ shared team) and, where local teams are allowed, every local team. A soul
1092
+ no pattern matches has its default only. A label in both files is a
1093
+ `team-label-collision` warning, and the shared definition wins. A local
1094
+ default no file declares is `E_TEAM_UNKNOWN`.
1068
1095
  - **Removed keys** are `E_WORKSPACE_SCHEMA` with `reason: "removed-key"`:
1096
+ `oats-local.yaml` `souls.teams` and `souls.default` (0.38.0);
1069
1097
  `messaging.byTeam`, `defaults.byTeam`, a soul.yaml `team`, an
1070
- oats-membership.yaml `team`, and `byTeam` in any provider payload layer.
1071
- Other payload keys are opaque (a `team` setting passes through).
1098
+ oats-membership.yaml `team`, and `byTeam` in any provider payload layer
1099
+ (0.30). Other payload keys are opaque (a `team` setting passes through).
1072
1100
 
1073
1101
  ### The team row (`TeamRow`)
1074
1102
 
1075
- Exactly `{label, team, default, from}`:
1103
+ Exactly `{label, team, default, from, via}`:
1076
1104
 
1077
1105
  ```json
1078
- [{"label":"antares","team":"antares:ana.aweb.ai","default":true,"from":"local"},
1079
- {"label":"oats","team":"oats:oats.aweb.ai","default":false,"from":"shared"},
1080
- {"label":"reviewers","team":null,"default":false,"from":"shared"}]
1106
+ [{"label":"security","team":"security:acme.aweb.ai","default":true,"from":"shared","via":["default"]},
1107
+ {"label":"docs","team":null,"default":false,"from":"shared","via":["workspace"]},
1108
+ {"label":"mine","team":"mine:ana.aweb.ai","default":false,"from":"local","via":["local"]}]
1081
1109
  ```
1082
1110
 
1083
1111
  `team` is `null` for an unmapped team. `default` is true on exactly the
1084
1112
  soul's default row; the others are teams an instance may join (offered, never
1085
- joined automatically). `from` is `"shared"` or `"local"`. The default row
1086
- comes first, then the rest by label (codepoint order). Reports include
1087
- unmapped rows; `OATS_TEAMS` and `instance.json.teams` carry mapped rows only.
1113
+ joined automatically). `from` is where the label is defined: `"shared"` or
1114
+ `"local"`. `via` is why the soul may join it, an ordered non-empty subset of
1115
+ `"default"`, `"workspace"` (its `souls:` pattern) and `"local"` (a local team
1116
+ where local teams are allowed). The default row comes first, then the rest by
1117
+ label (codepoint order). Reports include unmapped rows; `OATS_TEAMS` and
1118
+ `instance.json.teams` carry mapped rows only. A home spawned before 0.38.0
1119
+ recorded rows without `via`, and they are reported as recorded.
1088
1120
 
1089
1121
  ### The default (`DefaultTeam`)
1090
1122
 
1091
1123
  ```json
1092
- {"label":"antares","team":"antares:ana.aweb.ai","from":"deployment"}
1124
+ {"label":"security","team":"security:acme.aweb.ai","from":"soul"}
1093
1125
  ```
1094
1126
 
1095
- `from` is `"deployment"` (`defaultTeam`) or `"soul"` (`souls.default`). It is
1096
- `null` only when no default is configured (with messaging active, that is
1127
+ `from` is `"soul"` (the soul's `souls:` default in the workspace file; before
1128
+ 0.38.0 it meant `oats-local.yaml` `souls.default`), `"deployment"` (the local
1129
+ `defaultTeam`) or `"workspace"` (the workspace's `defaultTeam`). It is `null`
1130
+ only when no default is configured (with messaging active, that is
1097
1131
  `E_TEAM_UNCONFIGURED`). An unmapped default is `{label, team: null, from}`,
1098
1132
  the blocking problem `team-unmapped`.
1099
1133
 
@@ -1119,9 +1153,12 @@ its spawn-time default until respawned (readiness says so with
1119
1153
  the teams in `OATS_DEFAULT_TEAM`, `OATS_DEFAULT_TEAM_ID`,
1120
1154
  `OATS_DEFAULT_TEAM_FROM`, `OATS_TEAMS` and `OATS_TEAMS_SOURCE`:
1121
1155
  [capabilities.md](capabilities.md#teams-in-the-provider-environment).
1122
- `OATS_WORKSPACE_NAME` is the recorded workspace name (the discovered one for a
1123
- soul subject), `""` when unknown.
1156
+ `OATS_TEAMS` is exactly the soul's eligible mapped rows, so a provider that
1157
+ admits joins from it refuses any other team. `OATS_WORKSPACE_NAME` is the
1158
+ recorded workspace name (the discovered one for a soul subject), `""` when
1159
+ unknown.
1124
1160
 
1161
+ <a id="oats-teams"></a>
1125
1162
  ### `oats teams`
1126
1163
 
1127
1164
  ```text
@@ -1132,66 +1169,75 @@ oats teams default <label> --json
1132
1169
  ```
1133
1170
 
1134
1171
  ```json
1135
- {"teamsApi":1,"deployment":"/w","defaultTeam":"antares",
1136
- "teams":[{"label":"antares","team":"antares:ana.aweb.ai","description":null,"from":"local","default":true,"at":"oats-local.yaml#/teams/antares"},
1137
- {"label":"reviewers","team":null,"description":null,"from":"shared","default":false,"at":"github.com/awebai/oats:oats-workspace.yaml#/teams/reviewers"}],
1138
- "souls":{"teams":{"*":["oats"],"oats-expert":["reviewers"]},"default":{"oats-expert":"oats"}},
1139
- "problems":[{"code":"team-unmapped","label":"reviewers","default":false,"severity":"warning","at":"github.com/awebai/oats:oats-workspace.yaml#/teams/reviewers",
1140
- "message":"shared team reviewers has no provider id yet","fix":"its owner runs `oats aweb setup`, then commits the id"}]}
1172
+ {"teamsApi":2,"deployment":"/w","localTeams":true,"defaultTeam":{"label":"mine","team":"mine:ana.aweb.ai","from":"deployment"},
1173
+ "teams":[{"label":"mine","team":"mine:ana.aweb.ai","description":null,"from":"local","default":true,"at":"oats-local.yaml#/teams/mine"},
1174
+ {"label":"docs","team":null,"description":null,"from":"shared","default":false,"at":"github.com/acme/agents:oats-workspace.yaml#/teams/docs"}],
1175
+ "souls":{"*":{"teams":["docs"]},"security-souls/*":{"default":"security","teams":["engineering"]}},
1176
+ "problems":[{"code":"team-unmapped","label":"docs","default":false,"severity":"warning","at":"github.com/acme/agents:oats-workspace.yaml#/teams/docs",
1177
+ "message":"shared team docs has no provider id yet","fix":"its owner runs `oats aweb setup`, then commits the id"}]}
1141
1178
  ```
1142
1179
 
1143
- - `defaultTeam` is the deployment's label (or `null`), not a `DefaultTeam`.
1180
+ - `localTeams`: the workspace's answer (`true`/`false`), `null` in the
1181
+ standalone view. `defaultTeam` is the `DefaultTeam` a soul without a
1182
+ `souls:` default gets here (`from` `"deployment"` or `"workspace"`), or
1183
+ `null`.
1144
1184
  - `teams[]`: every declared team by label, `{label, team, description, from,
1145
- default, at}`; `at` is a pointer into `oats-local.yaml` or
1146
- `<workspace key>:oats-workspace.yaml#/teams/<label>`. A collision shows the
1147
- shared definition.
1148
- - `souls`: `souls.teams` and `souls.default` as written. `problems`: the
1149
- deployment's [team readiness items](#team-readiness-items).
1185
+ default, at}`; `default` marks that `defaultTeam`; `at` is a pointer into
1186
+ `oats-local.yaml` or `<workspace key>:oats-workspace.yaml#/teams/<label>`.
1187
+ A collision shows the shared definition.
1188
+ - `souls`: the workspace's `souls:` as committed (`{}` when none).
1189
+ `problems`: the deployment's [team readiness items](#team-readiness-items).
1190
+ The command discovers the workspace (every member's souls), for the
1191
+ `team-soul-unknown` check.
1150
1192
  - The verbs never call a provider. They validate, rewrite `oats-local.yaml` in
1151
1193
  place, and answer the document plus `changed: bool`.
1152
- - **`add`**: the first team added also becomes `defaultTeam`. A label already
1153
- declared is `E_TEAM_EXISTS {label, from}`; a bad label or no `--team` is
1154
- `E_BAD_ARGS`.
1155
- - **`remove`**: a referenced label is `E_TEAM_IN_USE {label, usedBy}` (each
1156
- `"defaultTeam"`, `"souls.teams:<key>"` or `"souls.default:<key>"`); a shared
1157
- label is `E_TEAM_SHARED {label, at}` (a label in both files can be removed
1158
- locally); unknown is `E_TEAM_UNKNOWN {label}`.
1194
+ - **Where local teams are not allowed**, `add` and `default` are refused
1195
+ before anything is written: `E_WORKSPACE_SCHEMA {reason:
1196
+ "local-teams-closed", path, keys}` (`keys` what the verb writes). `remove`
1197
+ still runs there: taking local teams away is the last step of committing
1198
+ them in the workspace.
1199
+ - **`add`**: the first team added also becomes the local `defaultTeam`. A
1200
+ label already declared is `E_TEAM_EXISTS {label, from}`; a bad label or no
1201
+ `--team` is `E_BAD_ARGS`.
1202
+ - **`remove`**: a label the local `defaultTeam` names is `E_TEAM_IN_USE
1203
+ {label, usedBy: ["defaultTeam"]}`; a shared label is `E_TEAM_SHARED {label,
1204
+ at}` (a label in both files can be removed locally); unknown is
1205
+ `E_TEAM_UNKNOWN {label}`.
1159
1206
  - **`default`**: any declared label, else `E_TEAM_UNKNOWN`.
1160
- - A write that would introduce an unknown or ineligible reference is refused
1161
- with that code; an invalid result is `E_WORKSPACE_SCHEMA`.
1207
+ - A write that would introduce an unknown reference is refused with that
1208
+ code; an invalid result is `E_WORKSPACE_SCHEMA`.
1209
+ - **Writes** edit `oats-local.yaml` in place and touch only the entries that
1210
+ change: comments and styles elsewhere, including inline comments on sibling
1211
+ entries, are kept. Each verb re-reads the file and judges its refusals on it
1212
+ as it is now, and writes only if the file did not change meanwhile (else it
1213
+ redoes the edit on the new content). A file that keeps changing is
1214
+ `E_LOCAL_CHANGED {path}`; nothing was written.
1162
1215
 
1216
+ <a id="oats-soul-teams"></a>
1163
1217
  ### `oats soul teams`
1164
1218
 
1165
1219
  ```text
1166
- oats soul teams <soul>|'*' [--add a,b] [--remove a,b] [--default <label> | --clear-default] [--dir <d>] --json
1167
- oats soul teams <soul>|'*' [--dir <d>] [--max-age <s>] --json (the read form only)
1220
+ oats soul teams <soul>|'*' [--dir <d>] [--max-age <s>] --json
1168
1221
  ```
1169
1222
 
1170
1223
  ```json
1171
- {"soulTeamsApi":1,"soul":"oats-expert","key":"oats-expert","defaultTeam":{"label":"oats","team":"oats:oats.aweb.ai","from":"soul"},
1172
- "teams":[{"label":"oats","team":"oats:oats.aweb.ai","default":true,"from":"shared","via":["default","*"]},
1173
- {"label":"reviewers","team":null,"default":false,"from":"shared","via":["soul"]}],
1174
- "local":{"teams":["reviewers"],"default":"oats"},"all":["oats"]}
1224
+ {"soulTeamsApi":2,"soul":"incident-responder","key":"security-souls/incident-responder",
1225
+ "match":"security-souls/incident-responder","defaultMatch":"security-souls/*",
1226
+ "defaultTeam":{"label":"security","team":"security:acme.aweb.ai","from":"soul"},
1227
+ "teams":[{"label":"security","team":"security:acme.aweb.ai","default":true,"from":"shared","via":["default"]},
1228
+ {"label":"docs","team":null,"default":false,"from":"shared","via":["workspace"]},
1229
+ {"label":"engineering","team":"engineering:acme.aweb.ai","default":false,"from":"shared","via":["workspace"]}]}
1175
1230
  ```
1176
1231
 
1177
- - Rows are `TeamRow` plus `via`, a non-empty ordered subset of `"default"`,
1178
- `"*"` and `"soul"`. `key` is the soul's `souls.*` key; `local` its own
1179
- entries; `all` is `souls.teams["*"]`.
1180
- - For `'*'`: `soul` and `key` are `"*"`, `teams` the deployment default plus
1181
- `souls.teams["*"]`, `local.default: null`.
1182
- - Mutations answer the document plus `changed`. Unknown label:
1183
- `E_TEAM_UNKNOWN {label}`. A `--default` outside the soul's teams:
1184
- `E_TEAM_NOT_ELIGIBLE {soul, label, at}` (`at`: `oats-local.yaml#/souls/default/<key>`,
1185
- `/` written `~1`). `--default`/`--clear-default` with
1186
- `'*'`, or both together: `E_BAD_ARGS`. Soul lookup: `E_SOUL_UNKNOWN`,
1187
- `E_SOUL_AMBIGUOUS`.
1188
- - **Writes** (`oats teams add|remove|default`, `oats soul teams`) edit
1189
- `oats-local.yaml` in place and touch only the entries that change: comments
1190
- and styles elsewhere, including inline comments on sibling entries and flow
1191
- lists, are kept. Each verb re-reads the file and judges its refusals on it
1192
- as it is now, and writes only if the file did not change meanwhile (else it
1193
- redoes the edit on the new content). A file that keeps changing is
1194
- `E_LOCAL_CHANGED {path}`; nothing was written.
1232
+ - Read only. `key` is the soul's key; `match` the `souls:` key its teams come
1233
+ from and `defaultMatch` the one its default comes from (`null`: none
1234
+ matches, or none sets a default). For `'*'`, `soul`, `key` and the only
1235
+ pattern tried are `"*"`. Soul lookup: `E_SOUL_UNKNOWN`, `E_SOUL_AMBIGUOUS`;
1236
+ the soul's teams: `E_WORKSPACE_SCHEMA` (local-teams-closed), `E_TEAM_UNKNOWN`.
1237
+ - The edit flags `--add`, `--remove`, `--default` and `--clear-default` were
1238
+ removed in 0.38.0: `E_BAD_ARGS` with details `{flag, replacement: "souls: in
1239
+ oats-workspace.yaml (a PR to the workspace file)"}`, before anything else
1240
+ is judged.
1195
1241
 
1196
1242
  ### The messaging provider's teams document
1197
1243
 
@@ -1218,8 +1264,9 @@ no `primary` and no `unmapped`: unmapped teams are kernel readiness items.
1218
1264
  `oats teams` lists them under `problems[]` as `{code, severity: "failure" |
1219
1265
  "warning", message, fix, …}`. `oats readiness` lists the soul's under
1220
1266
  `checks.configured` with `subject` `"team <label>"` (or `"teams"`), `producer:
1221
- "team model"`, `code`, `reason`, `remedy`, `status: "fail"`, `required: true`
1222
- for a failure and `false` for a warning, plus the problem's own keys.
1267
+ "team model"`, `code`, `reason` (the message), `remedy` (the fix), `status:
1268
+ "fail"`, `required: true` for a failure and `false` for a warning, plus the
1269
+ problem's own keys.
1223
1270
 
1224
1271
  | Code | Severity | Keys | When |
1225
1272
  |---|---|---|---|
@@ -1227,40 +1274,31 @@ for a failure and `false` for a warning, plus the problem's own keys.
1227
1274
  | `team-unmapped` | failure if `default`, else warning | `label`, `default`, `at` | a shared team without `team` |
1228
1275
  | `team-label-collision` | warning | `label`, `shared`, `local` (each `{team, description, at}`) | a label in both files |
1229
1276
  | `default-team-changed` | warning | `recorded`, `current` | `--home` with live teams: the default changed since the spawn |
1230
- | `E_TEAM_UNKNOWN` | failure | `label`, `at` | a reference to an undeclared label |
1231
- | `E_TEAM_NOT_ELIGIBLE` | failure | `soul`, `label`, `at` | `souls.default` outside the soul's teams |
1232
- | `team-model-3-migration` | warning | `condition`, `keys` | 0.36.x and 0.37.x: what OATS 0.38.0 (team model 3) refuses, one item per condition (below) |
1233
-
1234
- The `E_TEAM_UNKNOWN` and `E_TEAM_NOT_ELIGIBLE` codes are also spawn, preview and
1235
- inspect refusals, with the same details.
1236
-
1237
- `team-model-3-migration` is a deployment fact, so every soul's readiness
1238
- carries it, and `oats teams` lists it once. Its `condition`:
1239
-
1240
- - `local-soul-teams`: `oats-local.yaml` has `souls.teams` and/or
1241
- `souls.default` (`keys`: `["souls.teams", "souls.default"]` as found). They
1242
- move to `souls:` in `oats-workspace.yaml`.
1243
- - `local-teams-closed`: `oats-local.yaml` declares `teams` and/or
1244
- `defaultTeam` (`keys`: `["teams", "defaultTeam"]` as found) and the
1245
- workspace file does not say `localTeams: true`. The `fix` names both
1246
- remedies: add `localTeams: true` to the workspace file, or commit the teams
1247
- and `defaultTeam` there and remove them locally. Never raised in the
1248
- standalone view, which has no workspace rules.
1277
+ | `E_TEAM_UNKNOWN` | failure | `label`, `at` | a local default no file declares |
1278
+ | `E_WORKSPACE_SCHEMA` | failure | `condition: "local-teams-closed"`, `path`, `keys` | `oats-local.yaml` declares `teams` / `defaultTeam` and the workspace does not allow local teams |
1279
+ | `team-soul-unknown` | warning | `key`, `at` | a `souls:` key that is neither `"*"` nor `<name>/*` nor a discovered soul's key (a typo guard) |
1280
+
1281
+ `E_TEAM_UNKNOWN` and local-teams-closed are also spawn, preview and inspect
1282
+ refusals (local-teams-closed as `E_WORKSPACE_SCHEMA` with details `{reason,
1283
+ path, keys}`). While local teams are closed, that one item is the soul's only
1284
+ team item. `team-soul-unknown` is reported where the souls are discovered
1285
+ (`oats teams`, readiness).
1249
1286
 
1250
1287
  ```json
1251
- {"code":"team-model-3-migration","severity":"warning","condition":"local-teams-closed","keys":["teams","defaultTeam"],
1252
- "message":"oats-local.yaml declares teams, defaultTeam, but oats-workspace.yaml does not say localTeams: true: OATS 0.38.0 refuses local teams and a local defaultTeam unless the workspace allows them",
1288
+ {"code":"E_WORKSPACE_SCHEMA","severity":"failure","condition":"local-teams-closed","path":"oats-local.yaml","keys":["teams","defaultTeam"],
1289
+ "message":"oats-local.yaml declares teams, defaultTeam, but oats-workspace.yaml does not allow local teams (localTeams: true): either (a) add `localTeams: true` to oats-workspace.yaml, or (b) commit the teams and defaultTeam in oats-workspace.yaml, then remove them from oats-local.yaml",
1253
1290
  "fix":"either (a) add `localTeams: true` to oats-workspace.yaml, or (b) commit the teams and defaultTeam in oats-workspace.yaml, then remove them from oats-local.yaml"}
1254
1291
  ```
1255
1292
 
1256
- As a readiness item it is `{subject: "teams", status: "fail", required: false,
1257
- producer: "team model", code, reason: <message>, remedy: <fix>, condition,
1258
- keys}`. `oats doctor --json` lists the same problems under `problems[]`. Doctor
1259
- stays offline: for `local-teams-closed` it reads only the workspace file this
1260
- machine's parsed cache holds; when there is none, it adds no problem and says
1261
- so in `information[]`: `"team-model-3-migration: whether oats-local.yaml
1262
- teams/defaultTeam need localTeams: true couldn't be checked: this deployment
1263
- hasn't observed its workspace yet; run oats sync"`.
1293
+ `oats doctor --json` lists local-teams-closed under `problems[]` (text: `!
1294
+ E_WORKSPACE_SCHEMA (local-teams-closed): …`). Doctor stays offline: it reads
1295
+ only the workspace file this machine's parsed cache holds; when there is
1296
+ none, it adds no problem and says so in `information[]`:
1297
+ `"local-teams-closed: whether oats-workspace.yaml allows oats-local.yaml
1298
+ teams/defaultTeam (localTeams: true) couldn't be checked: this deployment
1299
+ hasn't observed its workspace yet; run oats sync"`. A removed key in
1300
+ `oats-local.yaml` makes doctor answer that refusal, as for any unreadable
1301
+ local file.
1264
1302
 
1265
1303
  <a id="soul-launch-preferences-feature-launch-preference-oats-0300"></a>
1266
1304
  ## Launch preferences
@@ -1613,7 +1651,7 @@ Feature `spawn-name`. `--name <slug>` is the exact name, with no prefix.
1613
1651
  | `E_SOUL_AMBIGUOUS` | `{name, repos, qualified}` | several souls answer the bare name; use one of `qualified` |
1614
1652
  | `E_SOUL_DISABLED` | `{name, qualifiedName, entry}` | listed in `souls.disabled` |
1615
1653
  | `E_UNKNOWN_AGENT` | | the resolved soul is not under the deployment's agents root |
1616
- | `E_TEAM_UNKNOWN`, `E_TEAM_NOT_ELIGIBLE` | `{label, at}`, `{soul, label, at}` | the soul's teams do not resolve |
1654
+ | `E_TEAM_UNKNOWN`, `E_WORKSPACE_SCHEMA` (local-teams-closed) | `{label, at}`, `{reason, path, keys}` | the soul's teams do not resolve |
1617
1655
  | `E_NOT_A_MEMBER`, `E_MEMBERSHIP_UNCONFIRMED` | | the soul's repository is not a confirmed member |
1618
1656
  | `E_CAPABILITY_MISSING`, `E_CAPABILITY_PRIVATE`, `E_CAPABILITY_INCOMPATIBLE`, `E_COMPATIBILITY` | | a capability cannot be resolved |
1619
1657
  | `E_PACKAGE_MISSING`, `E_PACKAGE_INTEGRITY`, `E_LOCK_SCHEMA` | | the lock does not provide it (standalone: `{…, standalone: true, reason: "no-catalog", catalog}`) |
package/docs/desktop.md CHANGED
@@ -89,10 +89,15 @@ new PATH.
89
89
 
90
90
  ## Opening a workspace
91
91
 
92
- The app starts on the directory it was launched with (its own folder by
93
- default). To view a deployment, open the workspace switcher in the sidebar
94
- and choose **Add workspace → Browse**, then point it at an OATS deployment —
95
- the directory (the operator's choice) holding `oats-local.yaml` and `agents/`.
92
+ The app opens the workspaces you had open, plus the directory it was
93
+ launched with (`--dir`, or the folder it was started from) when that is an
94
+ OATS deployment — the directory (the operator's choice) holding
95
+ `oats-local.yaml` and `agents/`. A folder that is not a deployment is never
96
+ opened: started from Finder, with nothing open to restore, the window shows
97
+ the workspace switcher instead. It lists the deployments on this computer
98
+ under **On this computer** (those directly inside `~/Agents`, and a saved
99
+ one that is not open); click one to open it in this window. **Add local
100
+ workspace… → Browse** points it at any other deployment.
96
101
  A picked folder without `oats-local.yaml` is offered onboarding instead. The
97
102
  Desktop never parses the deployment: its members, lock state and header come
98
103
  from `oats workspace status`, and its instances from the deployment's one
@@ -246,6 +251,21 @@ any other exit code. Closing the tab ends the reconnecting. Reconnecting
246
251
  never stops or restarts the agent on the server: only the local ssh viewer
247
252
  ends.
248
253
 
254
+ ## Copy from a terminal
255
+
256
+ Drag over an agent's output to select it. When you release the mouse, the
257
+ selection is copied to the clipboard, ready to paste anywhere. ⌘C, Edit › Copy
258
+ and the right-click menu then copy nothing new: the text is already there. The
259
+ selection is tmux's own (copy mode), so it can run past the visible screen
260
+ into the scrollback, and tmux sends its copy to the Desktop, which writes it to
261
+ the clipboard. Nothing in a terminal can read the clipboard.
262
+
263
+ If the agent's program uses the mouse itself, the drag goes to it instead. Hold
264
+ **Option** (macOS) or **Shift** while dragging to select in the terminal itself,
265
+ then copy with ⌘C or the right-click menu. If your `~/.tmux.conf` sets
266
+ `set-clipboard off`, tmux keeps its copies to itself: use Option-drag. ⌘V
267
+ pastes into the agent's draft as before.
268
+
249
269
  ## Attach files and screenshots
250
270
 
251
271
  Drop a file onto an agent terminal to insert its path into that agent's draft.
@@ -284,7 +304,7 @@ error in the terminal. Each drop/paste accepts up to 16 files totaling 25 MB.
284
304
  | "Compatible oats CLI required" card | No CLI, or a version outside the range the card itself states. Copy the card's install command, or **Choose oats…** to point at the right binary; **Retry** re-probes. Spawn is disabled until a compatible CLI is verified. |
285
305
  | Spawn disabled, no card | The probe hasn't settled yet (transient, resolves in ms). If it persists, the backend is unreachable — restart the app. |
286
306
  | Terminals fail to open ("could not attach") | tmux missing, or no live session for that instance. Install tmux (`tmux -V`); check `tmux ls`. |
287
- | Can't select/copy text in a terminal tab | The terminal runs with tmux mouse handling, so a plain drag scrolls/passes through. Hold **Option** (macOS) or **Shift** while dragging to make a local selection, then copy (Cmd+C / right-click → Copy). |
307
+ | Can't select/copy text in a terminal tab | A plain drag copies on release ([Copy from a terminal](#copy-from-a-terminal)). If nothing reaches the clipboard, the program in the pane has the mouse, or your tmux config sets `set-clipboard off`: hold **Option** (macOS) or **Shift** while dragging, then copy (Cmd+C / right-click → Copy). |
288
308
  | macOS "app is damaged / can't be opened" | Ad-hoc-signed (not notarized) build + quarantine. Right-click → Open, or clear the quarantine attribute (above). If it persists, verify the bundle: `codesign --verify --deep --strict --verbose=2 "/Applications/OATS Desktop.app"` — a non-zero exit means a broken artifact, report it. |
289
309
  | Roster empty | The opened directory isn't an OATS deployment (it needs `oats-local.yaml` and `agents/`). Use the workspace switcher → Add workspace to select the right folder. |
290
310
 
@@ -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.18.1
44
+ oats.aweb: v1.20.0
45
45
  oats.linear: v1.0.1
46
46
  oats.jira: v1.0.1
47
47
  defaults:
@@ -89,7 +89,10 @@ operations in full.
89
89
  `knowledge-maintainer` package soul reviews the resulting PRs.
90
90
  - **`oats.aweb`** mints a messaging identity for each instance at spawn and
91
91
  removes it at retire, contributes the aweb messaging skills, and wires the
92
- channel so sessions are woken by mail. `oats aweb roster` lists the team.
92
+ channel so sessions are woken by mail. `oats aweb roster` lists the team:
93
+ its membership certificates and workspaces, one entry per alias with its
94
+ sources, status and kind (global identities first), and says when either
95
+ source is incomplete.
93
96
  - **`oats.jira`** teaches the `jira-tasks` protocol and adds an advisory spawn
94
97
  hook that names the configured site and project.
95
98
  - **`oats.linear`** provides JSON-first `oats linear` commands, the
@@ -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": {