@awebai/oats 0.28.0 → 0.29.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.
Files changed (68) hide show
  1. package/bin/oats.mjs +296 -106
  2. package/capabilities/oats-okf/bin/oats-okf.mjs +28 -8
  3. package/capabilities/oats-okf/injects/okf.md +33 -33
  4. package/capabilities/oats-okf/lib/binding-wire.mjs +4 -1
  5. package/capabilities/oats-okf/lib/config.mjs +2 -1
  6. package/capabilities/oats-okf/lib/consult.mjs +1 -5
  7. package/capabilities/oats-okf/lib/harvest-status.mjs +88 -0
  8. package/capabilities/oats-okf/lib/harvest-switch.mjs +81 -0
  9. package/capabilities/oats-okf/lib/okf-validate.mjs +123 -0
  10. package/capabilities/oats-okf/lib/sources.mjs +28 -3
  11. package/capabilities/oats-okf/lib/stores.mjs +9 -4
  12. package/capabilities/oats-okf/lib/worker.mjs +82 -8
  13. package/capabilities/oats-okf/oats.json +14 -8
  14. package/capabilities/oats-okf/skills/okf-consultation/SKILL.md +8 -6
  15. package/capabilities/oats-okf/skills/okf-consultation/references/consult.md +1 -1
  16. package/capabilities/oats-okf/skills/okf-instance-knowledge/SKILL.md +104 -0
  17. package/capabilities/oats-okf-harvest/bin/okf-harvest.mjs +140 -0
  18. package/capabilities/oats-okf-harvest/injects/harvester.md +12 -0
  19. package/capabilities/oats-okf-harvest/oats.json +26 -0
  20. package/capabilities/oats-okf-harvest/skills/knowledge-harvest/SKILL.md +168 -0
  21. package/capabilities/oats-okf-harvest/skills/knowledge-theory/SKILL.md +192 -0
  22. package/capabilities/{oats-okf/skills/okf → oats-okf-harvest/skills/okf-authoring}/SKILL.md +15 -30
  23. package/capabilities/oats-okf-maintenance/bin/okf-maintenance.mjs +149 -0
  24. package/capabilities/oats-okf-maintenance/injects/maintainer.md +12 -0
  25. package/capabilities/oats-okf-maintenance/lib/provenance.mjs +45 -0
  26. package/capabilities/oats-okf-maintenance/oats.json +21 -0
  27. package/capabilities/oats-okf-maintenance/skills/knowledge-review/SKILL.md +144 -0
  28. package/capabilities/oats-okf-maintenance/skills/knowledge-theory/SKILL.md +192 -0
  29. package/capabilities/oats-okf-maintenance/skills/okf-authoring/SKILL.md +151 -0
  30. package/capabilities/oats-okf-maintenance/skills/okf-authoring/scripts/okf-validate.mjs +123 -0
  31. package/capabilities/oats-okf-maintenance/skills/okf-trigger-setup/SKILL.md +146 -0
  32. package/capabilities/oats-review/injects/review.md +3 -2
  33. package/capabilities/oats-review/oats.json +3 -4
  34. package/docs/capabilities.md +41 -9
  35. package/docs/capability-manifest.schema.json +0 -7
  36. package/docs/desktop-cli-api.md +257 -11
  37. package/docs/implementation.md +1 -1
  38. package/docs/knowledge-capability-authoring.md +8 -2
  39. package/docs/knowledge-reference/package-craft.md +8 -5
  40. package/docs/knowledge.md +101 -0
  41. package/docs/oats-local.schema.json +31 -1
  42. package/docs/official-catalog.md +7 -4
  43. package/docs/packages.md +11 -5
  44. package/docs/release-lane.md +1 -1
  45. package/docs/release-notes/v0.29.0.md +240 -0
  46. package/docs/schedules.md +133 -5
  47. package/docs/souls-and-instances.md +4 -6
  48. package/docs/workspaces.md +11 -2
  49. package/lib/automations.mjs +369 -0
  50. package/lib/core.mjs +65 -154
  51. package/lib/instance-inspect.mjs +12 -4
  52. package/lib/instance-resolution.mjs +31 -182
  53. package/lib/materialize.mjs +5 -7
  54. package/lib/operator-dispatch.mjs +1 -2
  55. package/lib/packages.mjs +17 -0
  56. package/lib/remote.mjs +21 -1
  57. package/lib/resolve.mjs +51 -7
  58. package/lib/schedule.mjs +211 -41
  59. package/lib/triggers.mjs +182 -49
  60. package/lib/workspace.mjs +1 -1
  61. package/package-catalog.json +6 -4
  62. package/package.json +1 -1
  63. package/capabilities/oats-okf/agents/memory-harvest/AGENTS.md +0 -26
  64. package/capabilities/oats-okf/agents/memory-harvest/soul.yaml +0 -5
  65. package/capabilities/oats-okf/skills/memory-harvest/SKILL.md +0 -285
  66. package/capabilities/oats-review/agents/reviewer/AGENTS.md +0 -53
  67. package/capabilities/oats-review/agents/reviewer/soul.yaml +0 -6
  68. /package/capabilities/{oats-okf/skills/okf → oats-okf-harvest/skills/okf-authoring}/scripts/okf-validate.mjs +0 -0
@@ -0,0 +1,146 @@
1
+ ---
2
+ name: okf-trigger-setup
3
+ description: >-
4
+ Set up and verify the OKF harvest-review trigger, which spawns a knowledge
5
+ maintainer for each harvest PR: declare it as a workspace automation
6
+ (`oats-triggers/okf-harvest-review.yaml` in a member repo, `from:
7
+ oats.okf:harvest-review`, with `runsOn` naming the one host and `owner` the
8
+ GitHub account that can merge on the knowledge-base repo), or locally with
9
+ `oats trigger add` for a machine-private setup. Covers the okf team, the
10
+ self-approval limit and `oats trigger test`. Use when setting up knowledge
11
+ operations, when harvest PRs are not being reviewed, when moving the
12
+ reviewer to another host, or when asked whether a host may run the
13
+ maintainer.
14
+ ---
15
+
16
+ # Setting up the harvest-review trigger
17
+
18
+ The trigger makes a harvest PR get reviewed: when a PR labelled `okf-harvest`
19
+ opens on the knowledge-base (KB) repository, the host tick spawns a new
20
+ `oats.okf/knowledge-maintainer` in the `okf` team to review it. It runs on one
21
+ machine, acting as one GitHub account, and that account must be able to
22
+ **merge** on the KB repository.
23
+
24
+ ## 1. Choose the host and the account
25
+
26
+ - **The account (`owner`)** must be able to merge on the KB repository (push,
27
+ maintain or admin). Check from the host:
28
+ `gh api repos/<owner>/<repo> --jq .permissions`.
29
+ - **The host (`runsOn`)** is the one machine that runs the trigger, named by
30
+ its `oats-local.yaml` `host: { name: <slug> }`, and logged in with `gh` as
31
+ that account.
32
+ - **The harvest switch is independent**: a review host need not harvest (its
33
+ `harvest` setting can stay `off`), and a harvesting host need not review.
34
+
35
+ ## 2. The self-approval limit
36
+
37
+ GitHub forbids approving your own account's PR. If the harvester's host and
38
+ the reviewer's account are the same GitHub account, then either:
39
+ - the KB repository's `main` must not require approving reviews (merge
40
+ permission is enough; the maintainer records its verdict as a PR comment,
41
+ not an approval), or
42
+ - the trigger's `owner` is a separate reviewer account or machine user.
43
+
44
+ Say which one applies when you report the setup.
45
+
46
+ ## 3. The okf team
47
+
48
+ The package souls carry `team: okf`. The workspace must declare it, with its
49
+ messaging mapping. Messaging is aweb (`oats.aweb`, the workspace default);
50
+ it needs oats.aweb 1.15.0 or later, which honours the `join=okf` the
51
+ harvester spawn and the trigger's `teams: [okf]` pass:
52
+
53
+ ```yaml
54
+ # oats-workspace.yaml
55
+ teams:
56
+ okf: { description: Knowledge operations }
57
+ defaults:
58
+ messaging: { oats.aweb: { from: package } }
59
+ messaging:
60
+ byTeam:
61
+ okf: { team: aweb:<your-org>.okf }
62
+ ```
63
+
64
+ Another messaging provider works the same way if it honours `join`.
65
+
66
+ Without it, the souls list with `E_TEAM_UNKNOWN`, and the harvester and the
67
+ maintainer cannot message each other.
68
+
69
+ ## 4. Declare it in the workspace (the default)
70
+
71
+ A team relies on the review, so declare it in Git, in a member repository (the
72
+ workspace's host repo), in its `oats-triggers/` folder:
73
+
74
+ ```yaml
75
+ # <member>/oats-triggers/okf-harvest-review.yaml
76
+ kind: oats-trigger
77
+ schemaVersion: 1
78
+ description: Review every OKF harvest PR on the knowledge base
79
+ from: oats.okf:harvest-review
80
+ set: { repo: github.com/<owner>/<kb-repo> } # optional: base, harness, model
81
+ runsOn: <host.name of the one machine>
82
+ owner: github.com/<the merge-capable account>
83
+ ```
84
+
85
+ - `kind: oats-trigger` and `schemaVersion: 1` make the file self-describing; a
86
+ wrong kind is `E_AUTOMATION_SCHEMA`.
87
+ - The id is `id:` if present, else the filename stem (`okf-harvest-review`).
88
+ Two files with the same id in one member are `E_AUTOMATION_DUPLICATE`.
89
+ - A file named `*.oats-trigger.yaml` anywhere in the member works too (never
90
+ under `oats-package/`, `.git/` or `node_modules/`); `oats-triggers/` is the
91
+ canonical place.
92
+
93
+ Or let the CLI write it: `oats trigger add --from oats.okf:harvest-review
94
+ --set repo=github.com/<owner>/<kb-repo> --workspace <member>` (it prints the
95
+ file when that repository is not the current checkout). Commit and merge it
96
+ like any other change.
97
+
98
+ A host runs it only when **both** its `host.name` equals `runsOn` **and** its
99
+ `gh` account equals `owner`. Everywhere else it is listed with the reason:
100
+ `assigned-elsewhere`, `owner-mismatch` or `host-unnamed`. That keeps exactly
101
+ one machine on it, and the operator's consent explicit. A host can opt out
102
+ without a commit: `automations.disabled: [<member>/okf-harvest-review]` in its
103
+ `oats-local.yaml`. Changes reach the host within about ten minutes (after
104
+ `oats sync`, or the tick's refresh).
105
+
106
+ ## 5. Or add it locally (machine-private)
107
+
108
+ For a personal or experimental setup, add it to this deployment only. It runs
109
+ on this host with this host's `gh`, with no `runsOn` or `owner`:
110
+
111
+ ```sh
112
+ oats trigger add --from oats.okf:harvest-review --set repo=github.com/<owner>/<kb-repo>
113
+ # optional: --set base=main --set harness=claude --set model=opus --id okf-harvest-review
114
+ ```
115
+
116
+ Never run the same review from two places: one workspace declaration, or one
117
+ local trigger on one host.
118
+
119
+ ## 6. Test it on the host that runs it
120
+
121
+ ```sh
122
+ oats trigger test <member>/okf-harvest-review # or: oats trigger test okf-harvest-review (local)
123
+ oats schedule host install # the host timer, if the test says it is missing
124
+ oats trigger status
125
+ ```
126
+
127
+ - `oats trigger test` checks gh auth and where its credential comes from, the
128
+ repository and your merge permissions, the soul, its messaging capability,
129
+ the okf team, the host/owner match, and what would fire now. It spawns
130
+ nothing. It must pass before you report the setup done; fix what it names.
131
+ - **Credentials reach the tick through the host timer, not your shell.** A
132
+ `GH_TOKEN` exported in your shell does not reach it; `gh auth login` with the
133
+ keyring or config file does.
134
+ - Pause with `oats trigger disable <id>`; `remove` leaves running maintainers
135
+ alone.
136
+
137
+ ## 7. Labels
138
+
139
+ Harvest PRs carry `okf-harvest` (the harvester's completion creates the label
140
+ if the repository lacks it). The maintainer adds `okf-needs-human` when a PR
141
+ would supersede a human-accepted decision. To pre-create both:
142
+
143
+ ```sh
144
+ gh label create okf-harvest --repo <owner>/<repo> --force --color 0E8A16 --description "OKF harvest PR (oats.okf)"
145
+ gh label create okf-needs-human --repo <owner>/<repo> --force --color D93F0B --description "OKF: needs a human decision"
146
+ ```
@@ -11,8 +11,9 @@ oats spawn reviewer --work attached --work-dir "$PWD/work" \
11
11
  --task "Review commit <sha> on branch <branch>. Report to <your-instance> per your operating loop."
12
12
  ```
13
13
 
14
- - `--purpose "<short-sha>"` gives the reviewer a unique, commit-relevant
15
- instance name (`reviewer-<short-sha>`); attached mode shares your work tree
14
+ - `reviewer` is the oats.dev package's soul (`oats.dev/reviewer`).
15
+ `--purpose "<short-sha>"` gives it a unique, commit-relevant instance name
16
+ (`oats-dev-reviewer-<short-sha>`); attached mode shares your work tree
16
17
  and automatically makes the reviewer your child (attached agents are always
17
18
  children of the work-tree owner — no relation flags needed or allowed).
18
19
  - The reviewer reviews **that commit's diff only** and reports its verdict
@@ -1,11 +1,10 @@
1
1
  {
2
2
  "capability": "oats.review",
3
3
  "private": true,
4
- "version": "1.2.1",
5
- "compatibility": { "oats": ">=0.26.0" },
6
- "description": "Post-commit review discipline: a fresh reviewer agent (defined by this capability) reviews each new commit's diff, reports its verdict to its spawner over the deployment's messaging layer (or in its transcript when there is none), and retires — plus the shared developer delivery discipline.",
4
+ "version": "1.3.0",
5
+ "compatibility": { "oats": ">=0.28.0" },
6
+ "description": "Post-commit review discipline: a fresh reviewer (this package's soul `reviewer`) reviews each new commit's diff, reports its verdict to its spawner over the deployment's messaging layer (or in its transcript when there is none), and retires — plus the shared developer delivery discipline and the reviewer's code-review and security-review skills.",
7
7
  "requires": [],
8
- "agents": ["agents/reviewer"],
9
8
  "skills": ["skills/code-review", "skills/security-review"],
10
9
  "inject": "injects/review.md"
11
10
  }
@@ -72,7 +72,7 @@ A self-contained package has an `oats.json`:
72
72
  - `compatibility.oats` is the kernel range the capability runs on. The kernel
73
73
  refuses to compose a capability whose range does not admit it
74
74
  (`E_CAPABILITY_INCOMPATIBLE`, naming capability, range and kernel) wherever a
75
- soul or capability agent resolves (spawn, `spawn --preview`, `inspect
75
+ soul resolves (spawn, `spawn --preview`, `inspect
76
76
  --soul`, operator commands). `oats inspect` shows each module's
77
77
  `compatibility: { ok, range, kernel }`; a home whose spawned module no longer
78
78
  admits the running kernel reports a `capability-incompatible` problem.
@@ -302,6 +302,19 @@ workspace's decision to trust it. A soul names a package capability with
302
302
  packages is in [packages.md](packages.md). There is no installed
303
303
  copy at a deployment and no `oats install`/`trust`/`update`/`remove`.
304
304
 
305
+ One package can carry capabilities meant for **different souls**. oats.okf
306
+ 4.0.0 ships three:
307
+
308
+ - `oats.okf` fills every working soul's knowledge slot;
309
+ - `oats.okf-harvest` is composed only into its harvester soul;
310
+ - `oats.okf-maintenance` is composed only into its maintainer soul.
311
+
312
+ Each is its own manifest with its own skills, inject and commands. One pin
313
+ versions all three, together with the package's souls
314
+ ([knowledge.md](knowledge.md#who-gets-which-okf-skills)). Split a package this
315
+ way when roles need different instructions: a soul composes only the
316
+ capability it names, so no role carries another's procedure.
317
+
305
318
  ## Member capabilities
306
319
 
307
320
  A capability at `<member repo>/capabilities/<name>/oats.json` is discoverable
@@ -314,13 +327,24 @@ listed (`private: true`, marked "(repo-owned)"), but usable only from its own
314
327
  repo (`E_CAPABILITY_PRIVATE` elsewhere). A member's `oats-package/` is **not** a member capability: it is
315
328
  reported as `publishes` and consumed only as a package.
316
329
 
317
- ## Capability-defined agents
330
+ ## Agents a capability needs
331
+
332
+ A capability declares no agents: `agents:` in a manifest was **removed in
333
+ 0.29.0**. Resolution refuses a module that still declares it with
334
+ `E_CAPABILITY_AGENTS_REMOVED { capability, agents }` (spawn, `spawn --preview`,
335
+ `inspect --soul`, operator commands). Ship the agent as a soul instead:
336
+
337
+ - a **package soul**: `souls/<name>/` beside the package's capabilities, listed
338
+ in `oats-package.json` `souls:`, spawned as `oats spawn <package>/<name>`
339
+ (or the bare name when unique), reading the package's capabilities with
340
+ `from: here` ([packages.md](packages.md#package-souls)). The post-commit
341
+ `reviewer` is one: oats.dev 1.1.0's `oats.dev/reviewer`, beside `oats.review`;
342
+ - or a **member soul**: `souls/<name>/` in a member repository, using a
343
+ member capability `from: here`.
318
344
 
319
- A manifest may declare `agents: ["agents/<name>"]` — package-relative soul
320
- directories (`soul.yaml` + `AGENTS.md` directly inside). *(Open thread: under
321
- the workspace model these are re-based on member souls — a package repo's
322
- expert soul is an ordinary `souls/<name>-expert/` in the member; the classic
323
- lookup still exists for 0.24 layouts.)*
345
+ A home an earlier kernel spawned from a manifest `agents:` soul (its agent
346
+ directory holds only `instances/`) is still listed by `oats status`, may anchor
347
+ a `--parent`, and retires; nothing creates one any more.
324
348
 
325
349
  ## Commands and hooks
326
350
 
@@ -335,7 +359,14 @@ existing manifests load, and change nothing.
335
359
 
336
360
  Hooks receive `OATS_EVENT`, `OATS_CAPABILITY`, `OATS_LAYER`, `OATS_INSTANCE`,
337
361
  `OATS_HOME`, `OATS_AGENT`, `OATS_SOUL`, `OATS_CONTEXT`, `OATS_WORKSPACE`,
338
- `OATS_ROOT`, `OATS_LEVEL`, `OATS_SETTINGS`, and `OATS_META`. A final JSON line may
362
+ `OATS_ROOT`, `OATS_LEVEL`, `OATS_SETTINGS`, `OATS_SETTINGS_ORIGINS`, and
363
+ `OATS_META`. `OATS_SETTINGS_ORIGINS` (0.29.0) says where each leaf of
364
+ `OATS_SETTINGS` came from: a JSON object from a JSON pointer to `{ kind, at }`,
365
+ `kind` being `manifest-default`, `workspace`, `soul`, `host`, `spawn` or
366
+ `anchor` (the last layer that set it), e.g.
367
+ `{"/harvest":{"kind":"soul","at":"soul.yaml#/knowledge"}}`. A provider tells a
368
+ soul-set value from a host-set one there, and never reads `soul.yaml` for it.
369
+ A home spawned before 0.29.0 recorded none: `{}`. A final JSON line may
339
370
  return `meta`, `brief`, `warning`, or harness-specific `launch` arguments. A
340
371
  **spawn hook only** may also return an `env` object for the launched process;
341
372
  returning `env` from retire or soul-scaffold is an explicit contract error.
@@ -482,7 +513,8 @@ passed as arguments; no shell is involved.
482
513
  - Every ambient `OATS_*`, `OAS_*` and `PI_*` variable is removed. Other
483
514
  variables pass through.
484
515
  - The kernel sets:
485
- - `OATS_CAPABILITY` and `OATS_SETTINGS` (the payload as JSON);
516
+ - `OATS_CAPABILITY`, `OATS_SETTINGS` (the payload as JSON) and
517
+ `OATS_SETTINGS_ORIGINS` (where each leaf came from, as hooks get it);
486
518
  - `OATS_CLI_BIN`;
487
519
  - `OATS_WORKSPACE` (the deployment);
488
520
  - the team variables `OATS_TEAM_ID` (the messaging payload's `team`: the
@@ -262,13 +262,6 @@
262
262
  },
263
263
  "additionalProperties": false
264
264
  },
265
- "agents": {
266
- "type": "array",
267
- "items": {
268
- "type": "string"
269
- },
270
- "description": "Package-relative soul directories (soul.yaml + AGENTS.md) \u2014 capability-defined agents; they resolve where the capability is active, souls stay read-only in the package, instances home under <deployment>/agents/<agent>/instances/."
271
- },
272
265
  "settings": {
273
266
  "type": "object",
274
267
  "description": "Declared capability settings: name to { default, values?, description }. Documentation for the values a soul, oats-local.yaml or `oats spawn --provider` supplies; undeclared settings are still accepted.",
@@ -96,7 +96,7 @@ routed commands (`--server`) already translate for such a host, sending
96
96
  | `oats inspect --json` `souls[]` rows, remote roster rows | `runtime` | `harness` | Output only; a pre-0.27 host's `runtime` rows are read as `harness` |
97
97
  | `oats inspect --home --json` `instance` | `runtime` | `harness` | Output only |
98
98
  | The launch plan's package check (`launch-config preview` `problems[]`) | `runtime-packages` | `harness-packages` | Output only |
99
- | Soul `soul.yaml` (member souls and capability-defined agents) | `runtime:` | `harness:` | Yes, **without** a warning: released capabilities (oats.aweb 1.13.1) ship `runtime:`, and the operator cannot fix a provider's file. Both, disagreeing → `E_BAD_MANIFEST` |
99
+ | Soul `soul.yaml` (member and package souls) | `runtime:` | `harness:` | Yes, **without** a warning: released capabilities (oats.aweb 1.13.1) ship `runtime:`, and the operator cannot fix a provider's file. Both, disagreeing → `E_BAD_MANIFEST` |
100
100
  | `oats-local.yaml` `launch-configs.<name>` | `runtime:` | `harness:` | Yes, with the warning. Both, disagreeing → `E_WORKSPACE_SCHEMA`. `launch-config set` writes `harness` (a `runtime` in its `--file` definition too) |
101
101
  | `launch-config list/preview --json` rows and `selection` | `runtime` | `harness` | Output only |
102
102
  | Home `instance.json` | `runtime`; launch recipe `launch` version 1 `{runtime}` | `harness`; recipe version **2** `{harness}` | Yes, with the warning naming the home: a 0.26.0 home inspects, starts, restarts and retires; its next start or restart records the new names |
@@ -253,6 +253,8 @@ payload the spawn recorded for a home, or the resolution computes for a soul.
253
253
  - `operations[].available` is `false` with a `reason` when it cannot run
254
254
  here: a `context: "home"` operation for a soul subject says `needs a
255
255
  running home (--home)`.
256
+ - `capabilities[].composedFrom` and `capabilitiesOff[]` (feature
257
+ `desktop-facts`): see [Desktop facts](#desktop-facts-feature-desktop-facts-oats-0290).
256
258
  - `instance` is `null` for a soul. For a home, `instructions.sources` names
257
259
  each composed inject in order.
258
260
  - A soul whose resolution is refused (for example, a package the lock does
@@ -400,7 +402,8 @@ on stdout:
400
402
  ```
401
403
 
402
404
  The environment is the provider's module environment:
403
- - `OATS_CAPABILITY`, `OATS_SETTINGS`, `OATS_CLI_BIN` and `OATS_WORKSPACE`;
405
+ - `OATS_CAPABILITY`, `OATS_SETTINGS`, `OATS_SETTINGS_ORIGINS` (0.29.0: JSON
406
+ pointer → `{ kind, at }`), `OATS_CLI_BIN` and `OATS_WORKSPACE`;
404
407
  - the team variables (`OATS_TEAM_*`, `OATS_WORKSPACE_NAME`/`_KEY`);
405
408
  - `OATS_AGENT` (the soul), and `OATS_SOUL` when the soul directory is known;
406
409
  - for a home, `OATS_INSTANCE` and `OATS_INSTANCE_HOME`.
@@ -755,7 +758,7 @@ the pre-fix marker and is never accepted for dispatch.
755
758
  `agents/<soul>/soul` pointer. (0.25.x previews populated the cache: that stated
756
759
  exception is gone.)
757
760
  - **Exact root**: `spawn <soul> --agents-root <abs>` binds the soul to that root
758
- (as inspect/readiness take it) — no team-soul / capability-agent / importable-
761
+ (as inspect/readiness take it) — no team-soul / importable-
759
762
  def fallback; mismatch → `E_SOUL_UNKNOWN`. The preview echoes
760
763
  `subject {soul, agentsRoot|null, dir|null}` **as given, byte-exact**.
761
764
  - **Decision binding**: `decision {instance, home, branch, base{ref,oid},
@@ -1190,6 +1193,9 @@ souls, paths, message }` — one `unmapped-team-label` per label that is in
1190
1193
  `teams:` but not in `messaging.byTeam`, naming its souls (sorted) and each
1191
1194
  soul's `<repoKey>:<path>#/team`; sorted by label. Never a problem.
1192
1195
 
1196
+ `defaults`, `clones`, `disabledSouls`, `lock`, file locations and
1197
+ `packages[].latest` (feature `desktop-facts`): see [Desktop facts](#desktop-facts-feature-desktop-facts-oats-0290).
1198
+
1193
1199
  `unsynced` = declared in `packages:` but not in the lock (run `sync`);
1194
1200
  `stale` = locked but no longer declared. Read-only: does not write the lock.
1195
1201
  (0.26.0: the `approval` object is gone with package approval.)
@@ -1238,6 +1244,10 @@ becomes `-`), and that directory is the agent `name` in `oats status --json`
1238
1244
  problem about a package soul carries `package` (and `repoKey: null`); its
1239
1245
  `path` is `package:<id>:<path in the repo>`.
1240
1246
 
1247
+ Souls rows' defaults, spawnability and `file`, and capability rows' `layer`,
1248
+ `description`, provides, `file` and `tree` (feature `desktop-facts`): see
1249
+ [Desktop facts](#desktop-facts-feature-desktop-facts-oats-0290).
1250
+
1241
1251
  Package capabilities of declared-but-unsynced packages are absent until `sync`.
1242
1252
  (`oats souls --json` keeps `soulsApi: 1` in 0.26.0: its shape is unchanged.
1243
1253
  The probe's `soulsApi` is **2** because it tracks the `oats inspect --json`
@@ -1385,13 +1395,15 @@ top-level `workspace` reachability field:
1385
1395
 
1386
1396
  Event-driven spawns of a deployment ([schedules.md#triggers](schedules.md#triggers)).
1387
1397
  Definitions live in `oats-schedules.json` (`kind: "trigger"`); `oats schedule list`
1388
- does not show them.
1398
+ does not show them. From 0.29.0 a row's `id` is qualified (`local/<id>` here; a
1399
+ workspace trigger's is `<member>/<id>`) and the row carries the shared fields of
1400
+ [workspace triggers and schedules](#workspace-triggers-and-schedules-feature-automations-oats-0290-automationsapi-1).
1389
1401
 
1390
1402
  ```json
1391
1403
  {"triggerApi":1,"scope":"/abs/deployment","triggers":[
1392
1404
  {"id":"okf-harvest-review","enabled":true,"kind":"trigger",
1393
1405
  "on":{"source":"github.pull_request","repo":"github.com/acme/knowledge","events":["opened","reopened","ready_for_review"],"labels":["okf-harvest"],"base":"main","poll":"2m"},
1394
- "spawn":{"soul":"oats.okf/knowledge-maintainer","purpose":"review-pr-{number}","task":"…","teams":["okf"],"harness":"claude","model":"opus"},
1406
+ "spawn":{"soul":"oats.okf/knowledge-maintainer","purpose":"review-pr-{number}","task":"…","teams":["okf"],"launchConfig":"reviewers","harness":"claude","model":"opus"},
1395
1407
  "concurrency":{"max":2,"perKey":1},"template":{"package":"oats.okf","version":"4.0.0","commit":"<oid>","template":"harvest-review"},
1396
1408
  "triggerApi":1,"scope":"/abs/deployment","createdAt":"<iso>","updatedAt":"<iso>"}]}
1397
1409
  ```
@@ -1399,18 +1411,24 @@ does not show them.
1399
1411
  - `list` → the document above; `show <id>`, `add`, `enable`, `disable` →
1400
1412
  `{ trigger }` (one row); a stored definition that no longer validates carries
1401
1413
  `invalid: { code, message }`. `remove <id>` → `{ removed, live: [instance] }`.
1402
- - `status [<id>]` → `{ triggerApi, scope, triggers: [{ id, enabled, repo, soul,
1403
- lastPoll: { at, ok, prs, matching } | { at, ok: false, error } | null,
1404
- nextPollAt, pending: [{ key, event, number, url, observedAt }], fired: [{ key,
1405
- at, instance, home, event, number }] (newest 50), firedTotal, live: [{
1406
- instance, home, repo, number, event }], lastError: { at, code, message, key? } | null }] }`.
1414
+ - `status [<id>]` → `{ triggerApi, scope, triggers: [Status] }`. It writes
1415
+ nothing. Each `Status`:
1416
+ - `id`, `name`, `enabled`, `runsHere`, `reason`, `enabledHere`, `repo`, `soul` (the soul name);
1417
+ - `concurrency: { max, perKey }` and `liveCount`: live instances against `max`;
1418
+ `live: [{ instance, home, repo, number, event }]`;
1419
+ - `lastPoll: { at, ok: true, prs, matching } | { at, ok: false, error } | null`;
1420
+ `nextPollAt`; `nextDue` (the next poll when it runs here, else `null`);
1421
+ - `pending: [{ key, event, number, url, observedAt }]`: observed, not yet spawned
1422
+ (held, or its spawn failed);
1423
+ - `fired: [{ key, at, instance, home, event, number }]` (newest 50) and `firedTotal`;
1424
+ - `lastError: { at, code, message, key? } | null`.
1407
1425
  - `test <id>` → `{ triggerApi, id, ok, gh: { ok, account, credentialSource:
1408
1426
  "keyring" | "config" | "env:<VAR>" | "unknown" | null, reachesHostTimer:
1409
1427
  boolean | null, note, detail }, repo: { key,
1410
1428
  readable, fullName, permissions: { push, maintain, admin }, canMerge } |
1411
1429
  { key, readable: false, error }, soul: { resolves, name, agent, messaging } |
1412
1430
  { resolves: false, name, error }, teams: { requested, undeclared | null,
1413
- messaging }, wouldFire: [{ key, event, number, url, held? }], pollError?, problems:
1431
+ messaging }, wouldFire: [{ key, repo, number, event, url, held? }], pollError?, problems:
1414
1432
  [string], warnings: [string], spawned: false }`. It writes nothing. `ok`
1415
1433
  counts `problems` only; a credential the host timer cannot reach
1416
1434
  (`reachesHostTimer: false`) is a warning.
@@ -1428,6 +1446,234 @@ does not show them.
1428
1446
  `E_TRIGGER_UNKNOWN`, `E_BAD_ARGS` (`missing` / `parameters` for a template),
1429
1447
  `E_PACKAGE_MISSING`, `E_PACKAGE_MANIFEST`, `E_LOCAL_MISSING`.
1430
1448
 
1449
+ ### Workspace triggers and schedules (feature `automations`, OATS 0.29.0; `automationsApi: 1`)
1450
+
1451
+ See [schedules.md#workspace-triggers-and-schedules](schedules.md#workspace-triggers-and-schedules).
1452
+ `oats trigger list --json` and `oats schedule list --json` answer this machine's
1453
+ local items and every workspace item defined in a member the user can read. The
1454
+ Desktop renders these rows and never re-derives them. The two lists stay separate:
1455
+ a trigger never appears in `schedule list`, and a schedule never appears in
1456
+ `trigger list`.
1457
+
1458
+ - Both lists add:
1459
+ - `host: { name | null, ghUser: { <gh host>: <login> | null } }`: this machine's
1460
+ `oats-local.yaml` `host.name`, and who its `gh` is logged in as on every GitHub
1461
+ host the rows name (the owners'; a trigger's repository's) — `null` when `gh`
1462
+ is not authenticated there. The Desktop compares it with a row's `owner`.
1463
+ - `snapshot: { takenAt, problems } | null` (`null` until `oats sync` has found some).
1464
+ - `scheduler: { installed, active, registered, lastTick, maxConcurrent, … }`: the
1465
+ host tick (the same object as `oats schedule host status`). Nothing runs unless
1466
+ it is installed, active and this deployment is registered.
1467
+ - **Identity:**
1468
+ - `id`:
1469
+ - a trigger row's is always qualified (`local/<id>`, `<member>/<id>`);
1470
+ - a local schedule row keeps its bare id (the 0.28 contract);
1471
+ - a workspace schedule row's is `<member>/<id>`.
1472
+ - `qualifiedId` is always the qualified form, and `name` is the bare id.
1473
+ - Every verb accepts `local/<id>` or a bare local id.
1474
+ - **Shared fields in every row:**
1475
+ - `origin`: where the item is defined, and where to open it:
1476
+ - `{ kind: "local", path: "oats-schedules.json", url: null, localPath }`;
1477
+ - `{ kind: "workspace", repoKey, path, commit, url, localPath }`: `url` is the
1478
+ file's web URL at `commit` (`https://github.com/<owner>/<repo>/blob/<commit>/<path>`
1479
+ for a `github.com` member, else `null`); `localPath` is the file in this
1480
+ machine's clone of the member (`null` when the member is not cloned here).
1481
+ - `description`, `owner`, `runsOn`;
1482
+ - `runsHere`; `reason` (`null` | `host-unnamed` | `assigned-elsewhere` | `owner-mismatch`) with `reasonDetail`;
1483
+ - `enabledHere`;
1484
+ - `soul`: `{ name, origin } | null` (`null` for a command, wake or operation schedule). `origin` is where the name resolves, per the snapshot:
1485
+ - `{ kind: "member", repoKey, member }`: a soul in a workspace member;
1486
+ - `{ kind: "package", package, version }`: a soul of a locked package;
1487
+ - `{ kind: "external", repoKey, source }`: an external soul (`source` is the workspace's `external[].source` ref);
1488
+ - `{ kind: "ambiguous", candidates }`: a bare name several souls answer to (`candidates` is how many); a spawn needs the qualified name;
1489
+ - `null`: not found (or no snapshot yet).
1490
+ - `task`: the template, verbatim;
1491
+ - `teams`, `launchConfig`, `harness`, `model`, `concurrency`;
1492
+ - `lastRun`, `nextDue`;
1493
+ - `invalid?: { code, message, field? }`.
1494
+ - **A trigger row** also carries `kind: "trigger"`, `on`, `spawn`, and `template?`:
1495
+ - `on: { source: "github.pull_request", repo: "<host>/<owner>/<repo>", events: [opened | reopened | ready_for_review | labeled | synchronize], labels: [string], base?: string, poll: "<n>s|m|h" }` (`base` absent: any base branch);
1496
+ - `spawn: { soul, purpose, task, teams: [label], launchConfig?, harness?, model?, yolo?, backend? }`
1497
+ (`purpose` and `task` are templates over `{repo}`, `{number}`, `{url}`, `{event}`, `{trigger}`, `{headSha}`;
1498
+ `launchConfig` names a launch configuration in the running host's `oats-local.yaml`);
1499
+ - `template?: { package, version, commit, template }`: the package template it was added from.
1500
+ - `lastRun` is the last fired event: `{ at, instance, home, event, number, key }`.
1501
+ - `nextDue` is the next poll, only when it runs here; `null` before its first poll (it polls at the next tick).
1502
+ - **A schedule row** keeps every 0.28 field (`scheduleApi: 2`). Its `kind` is the run (`spawn` | `command` | `wake` | `operation`); it also carries `cron` and `tz`.
1503
+ - `nextDue` is the next minute, only when it runs here.
1504
+ - `teams` is `[]` and `concurrency` is `null`.
1505
+ - A workspace schedule another host runs carries its definition and placement only: `lastRun` and `nextDue` are `null`.
1506
+ - A spawn schedule's `launchConfig` names a launch configuration in the running host's `oats-local.yaml`.
1507
+ - **Naming:** `nextDue` is the one name for "when it next runs" in every trigger and
1508
+ schedule row. A schedule row still carries the 0.24 `nextRun` for older readers;
1509
+ they agree whenever it runs here.
1510
+ - **Actions:**
1511
+ - `enable` and `disable` on a workspace id edit `oats-local.yaml` `triggers.disabled` or `schedules.disabled`.
1512
+ - `update` and `remove` refuse it with `E_AUTOMATION_WORKSPACE { id, origin }`.
1513
+ - `schedule run` and `schedule reconcile` work when it runs here, else `E_AUTOMATION_NOT_HERE { id, reason, runsOn, owner }`.
1514
+ - **`oats trigger test <id>`** adds `placement: { runsOn, owner, host, runsHere, reason, detail?, enabledHere }`. Any reason, or disabled here, is a problem (`ok: false`).
1515
+ - **`oats schedule test <id> --json`** (local or workspace) → `{ test: { id, qualifiedId, kind,
1516
+ placement: { runsHere, reason, reasonDetail?, enabledHere, runsOn, owner, host },
1517
+ soul: { name, origin, resolves, error: { code, message } | null } | null, nextDue,
1518
+ spawned: false, problems: [string], ok } }`. `soul` is checked the way the run
1519
+ would start it (`oats spawn <soul> --preview`, which writes nothing); it is `null`
1520
+ for a command, wake or operation. `nextDue` is the next cron match whether or not
1521
+ this host runs it (`placement` says that). Not running here, disabled, invalid or a
1522
+ soul that does not resolve is a problem (`ok: false`). It spawns nothing and records
1523
+ nothing. Errors: `E_SCHEDULE_UNKNOWN`, `E_BAD_ARGS`.
1524
+ - **`oats trigger|schedule add … --workspace <member> --runs-on <host> --owner <host>/<login> --json`** answers `{ id, written, file: { member, repoKey, path, content, written? } }`.
1525
+ - Errors: `E_AUTOMATION_MEMBER` (not a confirmed member), `E_TRIGGER_EXISTS` or `E_SCHEDULE_EXISTS` (the file exists), and the kind's validation codes.
1526
+ - **`oats automations refresh --json`** answers `{ automationsApi, snapshot, triggers, schedules, problems: [...], takenAt }`.
1527
+ - **`oats sync --json`** gains `automations: { triggers, schedules, problems, takenAt }`. Discovery problems join `problems` (`E_AUTOMATION_SCHEMA`, `E_AUTOMATION_DUPLICATE`, each with `kind`, `repoKey` and `path`).
1528
+ - **`oats workspace status --json`** gains `automations: { host, snapshot, rows: [{ kind, id, runsOn, owner, runsHere, reason, enabledHere, origin, invalid? }] }`.
1529
+ - **The tick's `considered[]`** gains the trigger action `not-here` (`reason: owner-mismatch`, `detail`): a trigger naming this host that this host cannot run. A workspace schedule's row `id` is its state key, `<member>~<id>`. A failed snapshot refresh is `{ action: "error", error: "automations refresh: …" }`.
1530
+
1531
+ ### Desktop facts (feature `desktop-facts`, OATS 0.29.0)
1532
+
1533
+ These are facts the Workspace view shows. The kernel reports them so the
1534
+ Desktop never works them out itself. Gate reading every field below on
1535
+ `desktop-facts` in `features[]`. No API integer changes, and every field is an
1536
+ addition to an existing row.
1537
+
1538
+ **`oats inspect --soul <name> --json`: why each capability is there**
1539
+
1540
+ - `capabilities[].composedFrom` says which layer put the module in the soul:
1541
+ `"workspace"` (`defaults.<slot>` or `defaults.capabilities`),
1542
+ `"team:<label>"` (`defaults.byTeam.<label>.capabilities`) or `"soul"` (the
1543
+ soul's own `capabilities:`). This is the same vocabulary as
1544
+ `layers.<slot>.from`. It is `null` on `inspect --home`, because a spawn does
1545
+ not record it. `from` stays the module's origin object (`{kind, repoKey,
1546
+ commit}` or the package object), so it is a separate key.
1547
+ - `capabilitiesOff[]` lists the capabilities the soul turned off, which a
1548
+ lower layer would otherwise have given it. They are not rows of
1549
+ `capabilities[]`, because those are resolved modules with operations. Each
1550
+ entry is `{ id, off: true, from: "soul", reason, slot?, overrides }`:
1551
+ - `reason: "off"`: the soul wrote `<id>: off` over a workspace or team
1552
+ default.
1553
+ - `reason: "slot-none"`: the soul wrote `<slot>: none` (`slot` names it),
1554
+ which emptied the slot the workspace filled with `<id>`.
1555
+ - `overrides`: the layer whose default was turned off (`"workspace"` or
1556
+ `"team:<label>"`).
1557
+ - Sorted by id. `[]` on `inspect --home`.
1558
+
1559
+ **`oats souls --json` rows**
1560
+
1561
+ - `harness`, `model`, `harnessFrom`: what a spawn of the soul starts with
1562
+ when no `--harness`/`--model` is given. A v2 `soul.yaml` cannot declare a
1563
+ harness or a model, so today this is always `harness: "pi"`, `model: null`
1564
+ (the harness's native model) and `harnessFrom: "kernel-default"`.
1565
+ `harnessFrom: "soul"` is reserved for a schema that lets a soul declare
1566
+ one.
1567
+ - `spawnable`, `problem`: whether a spawn here would refuse.
1568
+ - `problem` is `{ code, message }` when a spawn would refuse, else `null`.
1569
+ - The kernel resolves the soul exactly as a spawn does, but spawns nothing,
1570
+ writes nothing and reads only the sync cache.
1571
+ - Codes: `E_SOUL_DISABLED` (this machine's `souls.disabled`),
1572
+ `E_TEAM_CONFLICT`, `E_CAPABILITY_MISSING`, `E_CAPABILITY_PRIVATE`,
1573
+ `E_CAPABILITY_INCOMPATIBLE`, `E_PACKAGE_MISSING`, `E_PACKAGE_INTEGRITY`,
1574
+ `E_LOCK_SCHEMA`, `E_REMOTE_*`, and any other resolution refusal.
1575
+ - An `E_TEAM_UNKNOWN` problem in `problems[]` is informational. It does not
1576
+ make a soul unspawnable.
1577
+ - `file`: `{ path, url }`, the soul's `soul.yaml` in its repository (see
1578
+ **URLs** at the end of this section).
1579
+
1580
+ **`oats capabilities --json` rows**
1581
+
1582
+ - `layer` on every row. Package rows now carry it too, from the package
1583
+ manifest; `null` for a capability outside the slots.
1584
+ - `description`: the manifest's `description`, or `null`.
1585
+ - `skills`, `commands`, `hooks`: what the capability provides, by name,
1586
+ sorted.
1587
+ - `skills` is enumerated as a spawn would. It is `null` when the declared
1588
+ skills cannot be listed, which a spawn of it would refuse.
1589
+ - `commands` and `hooks` are the keys of the manifest's `commands` and
1590
+ `hooks`.
1591
+ - `file`: `{ path, url }`, the capability's `oats.json`, or `null` when the
1592
+ manifest cannot be read.
1593
+ - `tree`: a member capability's fingerprint, the Git tree id of its
1594
+ directory at the member commit. The same bytes give the same id. It is
1595
+ `null` on package rows, whose fingerprint is `integrity` in the lock (see
1596
+ `oats workspace status`).
1597
+ - A package whose manifests cannot be read at its locked commit leaves these
1598
+ facts `null` on its rows.
1599
+ - Package manifests are read at the locked commit from the sync cache. There
1600
+ is no network beyond what `sync` already fetched.
1601
+
1602
+ **`oats workspace status --json`**
1603
+
1604
+ ```json
1605
+ {"workspace":{"…":"…","file":{"path":"oats-workspace.yaml","url":"https://github.com/acme/agents/blob/<oid>/oats-workspace.yaml"}},
1606
+ "members":[{"…":"…","url":"https://github.com/acme/tools/tree/<oid>","membershipFile":{"path":"oats-membership.yaml","url":"https://github.com/acme/tools/blob/<oid>/oats-membership.yaml"}}],
1607
+ "packages":[{"id":"oats.okf","version":"3.0.0","source":"catalog:oats.okf","commit":"<oid>","…":"…","latest":{"version":"4.0.0","ref":"v4.0.0"}}],
1608
+ "defaults":{"slots":{"knowledge":{"name":"oats.okf","from":"package"},"messaging":"none","tasks":null},
1609
+ "capabilities":[{"name":"acme-house-style","from":"github.com/acme/agents","off":false}],
1610
+ "byTeam":{"engineering":{"capabilities":[{"name":"acme-house-style","from":null,"off":true},{"name":"acme-deploy","from":"package","off":false}]}}},
1611
+ "clones":[{"key":"github.com/acme/agents","name":"agents","path":"/abs/acme-workspace/agents","rule":"convention"},
1612
+ {"key":"github.com/acme/tools","name":"tools","path":null,"rule":null}],
1613
+ "disabledSouls":["release-reviewer"],
1614
+ "lock":{"path":"/abs/acme-workspace/oats-lock.json","lockfileVersion":3}}
1615
+ ```
1616
+
1617
+ - `defaults`: the workspace file's defaults, as declared rather than
1618
+ resolved for a soul.
1619
+ - `slots.<slot>` is `{ name, from }` when the workspace fills it, `"none"`
1620
+ when it empties it, and `null` when it says nothing.
1621
+ - `capabilities` and `byTeam.<label>.capabilities` are rows `{ name, from,
1622
+ off }`, sorted by name. `from` is the declared location (`"package"`,
1623
+ `"here"` or a member repo key); an `off` row has `from: null`.
1624
+ - Standalone: slots `null`, `capabilities: []` and `byTeam: {}`.
1625
+ - `clones`: this computer's clone of each member.
1626
+ - `path` is the absolute clone, or `null` when this machine has none.
1627
+ - `rule` names what found it: `"clones"` (the `oats-local.yaml` `clones:`
1628
+ entry) or `"convention"` (`<deployment>/<member name>`). It is `null`
1629
+ with no clone.
1630
+ - A path that is not the member's clone gives `path: null, rule: null,
1631
+ problem: { code: "E_CLONE_MISMATCH", message }`, the refusal a spawn
1632
+ would meet.
1633
+ - (`--repo` is a spawn option, so it plays no part here.)
1634
+ - `disabledSouls`: `oats-local.yaml` `souls.disabled`, as written.
1635
+ - `lock`: `{ path, lockfileVersion }`. The per-package commit is each
1636
+ `packages[]` row's `commit`, and its fingerprint is `integrity`.
1637
+ - `packages[].latest`: `{ version, ref }` when the official catalog shipped
1638
+ with this kernel has a newer version of a catalog-sourced package than the
1639
+ lock holds. It is `null` when the pin is current and for `git:` packages.
1640
+ It never reaches the network: the catalog is the kernel's own
1641
+ (`OATS_PACKAGE_CATALOG` overrides it, as for `sync`).
1642
+ - `workspace.file` is `{ path, url }` for the workspace file in the
1643
+ workspace repository (at `workspace.key` @ `workspace.commit`). It is
1644
+ `null` for a standalone deployment.
1645
+ - `members[].url` is the member repository at its commit.
1646
+ `members[].membershipFile` is `{ path, url }`.
1647
+
1648
+ **`oats status --json` instance rows**
1649
+
1650
+ - A member module's `modules[].current` gains `version` (the capability's
1651
+ manifest version at the current commit, `null` when it has none) beside
1652
+ `commit`, on `current` and `moved` rows. Package rows already carried it. On a `moved` row, the recorded `commit`/`from` and `current`
1653
+ together say what moved and to what.
1654
+ - `startedAt`: the last session start or restart (the session receipt). A
1655
+ home spawned with a launch and never restarted uses `createdAt`. A home
1656
+ never launched is `null`. `createdAt` stays the spawn time.
1657
+ - `modelFrom`: where the model the home runs came from.
1658
+ - `"soul"`: the soul's model preference.
1659
+ - `"spawn"` or `"start"`: an explicit `--model` on that command.
1660
+ - `"launch-config"`: a launch configuration's model.
1661
+ - `"harness-default"`: the harness's own model.
1662
+ - A start that reuses the recorded model keeps the recorded answer.
1663
+ - `null` for a home spawned before 0.29.0. `instance.json` records it as
1664
+ `modelFrom`.
1665
+ - `identityAddress`: the messaging identity's `address` (else `alias`) that
1666
+ the messaging capability recorded (`capabilityMeta.<messaging>.identity`),
1667
+ passed through unchanged. `null` otherwise.
1668
+
1669
+ **URLs.** Every `url` is a browsable page of
1670
+ the file (or of the repository, for a member) at the commit the row names.
1671
+ Only repositories on `github.com` have one (`https://github.com/<org>/<repo>/blob/<commit>/<path>`,
1672
+ or `/tree/<commit>`). Every other host and local repository gives `url:
1673
+ null`, with `path` still set. `path` is relative to that repository's root.
1674
+
1675
+ **Help.** `oats help` lists `spawn … [--provider <capability> <key>=<value>]`.
1676
+
1431
1677
  ### Eligible teams (feature `teams`, OATS 0.26.0)
1432
1678
 
1433
1679
  A soul's `team` may be a list of labels; the first is the primary
@@ -113,7 +113,7 @@ what they contribute stays with them.
113
113
 
114
114
  After the canonical soul and kernel text, every generated `AGENTS.md` states the
115
115
  runtime-neutral **home/work boundary** (`injects/instance-boundary.md`) — for
116
- every work mode and for capability service agents alike — immediately before the
116
+ every work mode and for service souls (the post-commit reviewer) alike — immediately before the
117
117
  work-mode block it frames: `<instance-home>` (`$OATS_INSTANCE_HOME`) holds the
118
118
  brain, task, provenance and working state, and is where OATS operational/lifecycle
119
119
  commands are run from — together with the commands of whatever capabilities are