@awebai/oats 0.28.0 → 0.29.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.
Files changed (70) hide show
  1. package/bin/oats.mjs +298 -107
  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 +277 -15
  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/release-notes/v0.29.1.md +79 -0
  47. package/docs/schedules.md +133 -5
  48. package/docs/souls-and-instances.md +4 -6
  49. package/docs/workspaces.md +11 -2
  50. package/lib/automations.mjs +369 -0
  51. package/lib/core.mjs +65 -154
  52. package/lib/instance-git.mjs +30 -1
  53. package/lib/instance-inspect.mjs +12 -4
  54. package/lib/instance-resolution.mjs +31 -182
  55. package/lib/materialize.mjs +5 -7
  56. package/lib/operator-dispatch.mjs +1 -2
  57. package/lib/packages.mjs +17 -0
  58. package/lib/remote.mjs +21 -1
  59. package/lib/resolve.mjs +51 -7
  60. package/lib/schedule.mjs +211 -41
  61. package/lib/triggers.mjs +182 -49
  62. package/lib/workspace.mjs +1 -1
  63. package/package-catalog.json +6 -4
  64. package/package.json +1 -1
  65. package/capabilities/oats-okf/agents/memory-harvest/AGENTS.md +0 -26
  66. package/capabilities/oats-okf/agents/memory-harvest/soul.yaml +0 -5
  67. package/capabilities/oats-okf/skills/memory-harvest/SKILL.md +0 -285
  68. package/capabilities/oats-review/agents/reviewer/AGENTS.md +0 -53
  69. package/capabilities/oats-review/agents/reviewer/soul.yaml +0 -6
  70. /package/capabilities/{oats-okf/skills/okf → oats-okf-harvest/skills/okf-authoring}/scripts/okf-validate.mjs +0 -0
@@ -19,9 +19,12 @@ prints exactly one JSON object on stdout:
19
19
 
20
20
  `version` is the installed package's exact semver (e.g. `0.20.0`).
21
21
  The Desktop accepts `desktopApi === 1` and gates on the kernel feature
22
- `packages-no-approval` (semver range `>=0.25.8 <0.27.0`: the floor admits the
23
- main-branch kernel before 0.26.0 is tagged; the feature fence is the real gate).
24
- Earlier bands were `>=0.22.0 <0.26.0` (Desktop 0.25) and `>=0.22.0 <0.24.0`
22
+ `packages-no-approval` (semver range `>=0.25.8 <0.30.0`, spelled once in
23
+ `packages/desktop/cli-locator.mjs` `ACCEPT_RANGE`: the floor admits the
24
+ main-branch kernel before 0.26.0 was tagged; the feature fences are the real gate,
25
+ and 0.29's reads are gated on `automations` and `desktop-facts`).
26
+ Earlier bands were `>=0.25.8 <0.29.0` (Desktop 0.28), `<0.28.0` (Desktop 0.27),
27
+ `<0.27.0` (Desktop 0.26), `>=0.22.0 <0.26.0` (Desktop 0.25) and `>=0.22.0 <0.24.0`
25
28
  (Desktop 0.23). It does not establish complete
26
29
  UI, backend, plugin, retirement or recovery parity; capability checks and explicit
27
30
  refusals below remain authoritative.
@@ -96,7 +99,7 @@ routed commands (`--server`) already translate for such a host, sending
96
99
  | `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
100
  | `oats inspect --home --json` `instance` | `runtime` | `harness` | Output only |
98
101
  | 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` |
102
+ | 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
103
  | `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
104
  | `launch-config list/preview --json` rows and `selection` | `runtime` | `harness` | Output only |
102
105
  | 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 +256,8 @@ payload the spawn recorded for a home, or the resolution computes for a soul.
253
256
  - `operations[].available` is `false` with a `reason` when it cannot run
254
257
  here: a `context: "home"` operation for a soul subject says `needs a
255
258
  running home (--home)`.
259
+ - `capabilities[].composedFrom` and `capabilitiesOff[]` (feature
260
+ `desktop-facts`): see [Desktop facts](#desktop-facts-feature-desktop-facts-oats-0290).
256
261
  - `instance` is `null` for a soul. For a home, `instructions.sources` names
257
262
  each composed inject in order.
258
263
  - A soul whose resolution is refused (for example, a package the lock does
@@ -400,7 +405,8 @@ on stdout:
400
405
  ```
401
406
 
402
407
  The environment is the provider's module environment:
403
- - `OATS_CAPABILITY`, `OATS_SETTINGS`, `OATS_CLI_BIN` and `OATS_WORKSPACE`;
408
+ - `OATS_CAPABILITY`, `OATS_SETTINGS`, `OATS_SETTINGS_ORIGINS` (0.29.0: JSON
409
+ pointer → `{ kind, at }`), `OATS_CLI_BIN` and `OATS_WORKSPACE`;
404
410
  - the team variables (`OATS_TEAM_*`, `OATS_WORKSPACE_NAME`/`_KEY`);
405
411
  - `OATS_AGENT` (the soul), and `OATS_SOUL` when the soul directory is known;
406
412
  - for a home, `OATS_INSTANCE` and `OATS_INSTANCE_HOME`.
@@ -480,7 +486,7 @@ un-materialized tree → `E_NO_WORKTREE`.
480
486
  "base":{"ref":"origin/main","source":"origin/HEAD","mergeBase":"<oid>","ahead":2,"behind":0},
481
487
  "remote":{"name":"origin","url":"git@github.com:acme/one.git","host":"github.com","path":"acme/one","source":"branch-upstream|origin"},
482
488
  "summary":{"changed":1,"renamed":1,"copied":0,"unmerged":0,"untracked":1},
483
- "files":[{"id":"<24 hex>","kind":"renamed","xy":"R.","submodule":false,"score":"R100","path":"src/new.txt","origPath":"src/old.txt"}],
489
+ "files":[{"id":"<24 hex>","kind":"renamed","xy":"R.","submodule":false,"score":"R100","path":"src/new.txt","origPath":"src/old.txt","additions":84,"deletions":3,"binary":false}],
484
490
  "notes":[]}
485
491
  ```
486
492
 
@@ -493,6 +499,19 @@ un-materialized tree → `E_NO_WORKTREE`.
493
499
  unmerged | untracked. Ignored files are not listed.
494
500
  - `files[].id` is **opaque**, minted under (`revision`, `indexRevision`). It is
495
501
  the only way to ask for a diff.
502
+ - `files[].additions`, `deletions` and `binary` (0.29.1, additive;
503
+ `instanceGitApi` stays 1) are each file's line counts.
504
+ - They come from one `git diff <revision> --numstat -z -M` per observation:
505
+ the working tree against the observed commit, **staged and unstaged
506
+ combined**. That is the baseline of the status letters and of
507
+ `oats instance diff`.
508
+ - An unborn tree counts against the empty tree. A rename counts on its new
509
+ `path`.
510
+ - A binary file is `additions: null, deletions: null, binary: true`. An
511
+ untracked file (no baseline; its contents are not read) and a submodule
512
+ are all `null`.
513
+ - If the count itself fails, every entry is `null` and `notes` says line
514
+ counts are unavailable. `null` means unknown, never zero.
496
515
  - `remote` (0.24.8+): the branch's configured remote (`source: branch-upstream`),
497
516
  else `origin`, else `null` — never invented. `host`/`path` are **parsed** from
498
517
  the URL (ssh/https forms; `.git` stripped) so an ADE can choose a forge backend
@@ -755,7 +774,7 @@ the pre-fix marker and is never accepted for dispatch.
755
774
  `agents/<soul>/soul` pointer. (0.25.x previews populated the cache: that stated
756
775
  exception is gone.)
757
776
  - **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-
777
+ (as inspect/readiness take it) — no team-soul / importable-
759
778
  def fallback; mismatch → `E_SOUL_UNKNOWN`. The preview echoes
760
779
  `subject {soul, agentsRoot|null, dir|null}` **as given, byte-exact**.
761
780
  - **Decision binding**: `decision {instance, home, branch, base{ref,oid},
@@ -1190,6 +1209,9 @@ souls, paths, message }` — one `unmapped-team-label` per label that is in
1190
1209
  `teams:` but not in `messaging.byTeam`, naming its souls (sorted) and each
1191
1210
  soul's `<repoKey>:<path>#/team`; sorted by label. Never a problem.
1192
1211
 
1212
+ `defaults`, `clones`, `disabledSouls`, `lock`, file locations and
1213
+ `packages[].latest` (feature `desktop-facts`): see [Desktop facts](#desktop-facts-feature-desktop-facts-oats-0290).
1214
+
1193
1215
  `unsynced` = declared in `packages:` but not in the lock (run `sync`);
1194
1216
  `stale` = locked but no longer declared. Read-only: does not write the lock.
1195
1217
  (0.26.0: the `approval` object is gone with package approval.)
@@ -1238,6 +1260,10 @@ becomes `-`), and that directory is the agent `name` in `oats status --json`
1238
1260
  problem about a package soul carries `package` (and `repoKey: null`); its
1239
1261
  `path` is `package:<id>:<path in the repo>`.
1240
1262
 
1263
+ Souls rows' defaults, spawnability and `file`, and capability rows' `layer`,
1264
+ `description`, provides, `file` and `tree` (feature `desktop-facts`): see
1265
+ [Desktop facts](#desktop-facts-feature-desktop-facts-oats-0290).
1266
+
1241
1267
  Package capabilities of declared-but-unsynced packages are absent until `sync`.
1242
1268
  (`oats souls --json` keeps `soulsApi: 1` in 0.26.0: its shape is unchanged.
1243
1269
  The probe's `soulsApi` is **2** because it tracks the `oats inspect --json`
@@ -1385,13 +1411,15 @@ top-level `workspace` reachability field:
1385
1411
 
1386
1412
  Event-driven spawns of a deployment ([schedules.md#triggers](schedules.md#triggers)).
1387
1413
  Definitions live in `oats-schedules.json` (`kind: "trigger"`); `oats schedule list`
1388
- does not show them.
1414
+ does not show them. From 0.29.0 a row's `id` is qualified (`local/<id>` here; a
1415
+ workspace trigger's is `<member>/<id>`) and the row carries the shared fields of
1416
+ [workspace triggers and schedules](#workspace-triggers-and-schedules-feature-automations-oats-0290-automationsapi-1).
1389
1417
 
1390
1418
  ```json
1391
1419
  {"triggerApi":1,"scope":"/abs/deployment","triggers":[
1392
1420
  {"id":"okf-harvest-review","enabled":true,"kind":"trigger",
1393
1421
  "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"},
1422
+ "spawn":{"soul":"oats.okf/knowledge-maintainer","purpose":"review-pr-{number}","task":"…","teams":["okf"],"launchConfig":"reviewers","harness":"claude","model":"opus"},
1395
1423
  "concurrency":{"max":2,"perKey":1},"template":{"package":"oats.okf","version":"4.0.0","commit":"<oid>","template":"harvest-review"},
1396
1424
  "triggerApi":1,"scope":"/abs/deployment","createdAt":"<iso>","updatedAt":"<iso>"}]}
1397
1425
  ```
@@ -1399,18 +1427,24 @@ does not show them.
1399
1427
  - `list` → the document above; `show <id>`, `add`, `enable`, `disable` →
1400
1428
  `{ trigger }` (one row); a stored definition that no longer validates carries
1401
1429
  `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 }] }`.
1430
+ - `status [<id>]` → `{ triggerApi, scope, triggers: [Status] }`. It writes
1431
+ nothing. Each `Status`:
1432
+ - `id`, `name`, `enabled`, `runsHere`, `reason`, `enabledHere`, `repo`, `soul` (the soul name);
1433
+ - `concurrency: { max, perKey }` and `liveCount`: live instances against `max`;
1434
+ `live: [{ instance, home, repo, number, event }]`;
1435
+ - `lastPoll: { at, ok: true, prs, matching } | { at, ok: false, error } | null`;
1436
+ `nextPollAt`; `nextDue` (the next poll when it runs here, else `null`);
1437
+ - `pending: [{ key, event, number, url, observedAt }]`: observed, not yet spawned
1438
+ (held, or its spawn failed);
1439
+ - `fired: [{ key, at, instance, home, event, number }]` (newest 50) and `firedTotal`;
1440
+ - `lastError: { at, code, message, key? } | null`.
1407
1441
  - `test <id>` → `{ triggerApi, id, ok, gh: { ok, account, credentialSource:
1408
1442
  "keyring" | "config" | "env:<VAR>" | "unknown" | null, reachesHostTimer:
1409
1443
  boolean | null, note, detail }, repo: { key,
1410
1444
  readable, fullName, permissions: { push, maintain, admin }, canMerge } |
1411
1445
  { key, readable: false, error }, soul: { resolves, name, agent, messaging } |
1412
1446
  { resolves: false, name, error }, teams: { requested, undeclared | null,
1413
- messaging }, wouldFire: [{ key, event, number, url, held? }], pollError?, problems:
1447
+ messaging }, wouldFire: [{ key, repo, number, event, url, held? }], pollError?, problems:
1414
1448
  [string], warnings: [string], spawned: false }`. It writes nothing. `ok`
1415
1449
  counts `problems` only; a credential the host timer cannot reach
1416
1450
  (`reachesHostTimer: false`) is a warning.
@@ -1428,6 +1462,234 @@ does not show them.
1428
1462
  `E_TRIGGER_UNKNOWN`, `E_BAD_ARGS` (`missing` / `parameters` for a template),
1429
1463
  `E_PACKAGE_MISSING`, `E_PACKAGE_MANIFEST`, `E_LOCAL_MISSING`.
1430
1464
 
1465
+ ### Workspace triggers and schedules (feature `automations`, OATS 0.29.0; `automationsApi: 1`)
1466
+
1467
+ See [schedules.md#workspace-triggers-and-schedules](schedules.md#workspace-triggers-and-schedules).
1468
+ `oats trigger list --json` and `oats schedule list --json` answer this machine's
1469
+ local items and every workspace item defined in a member the user can read. The
1470
+ Desktop renders these rows and never re-derives them. The two lists stay separate:
1471
+ a trigger never appears in `schedule list`, and a schedule never appears in
1472
+ `trigger list`.
1473
+
1474
+ - Both lists add:
1475
+ - `host: { name | null, ghUser: { <gh host>: <login> | null } }`: this machine's
1476
+ `oats-local.yaml` `host.name`, and who its `gh` is logged in as on every GitHub
1477
+ host the rows name (the owners'; a trigger's repository's) — `null` when `gh`
1478
+ is not authenticated there. The Desktop compares it with a row's `owner`.
1479
+ - `snapshot: { takenAt, problems } | null` (`null` until `oats sync` has found some).
1480
+ - `scheduler: { installed, active, registered, lastTick, maxConcurrent, … }`: the
1481
+ host tick (the same object as `oats schedule host status`). Nothing runs unless
1482
+ it is installed, active and this deployment is registered.
1483
+ - **Identity:**
1484
+ - `id`:
1485
+ - a trigger row's is always qualified (`local/<id>`, `<member>/<id>`);
1486
+ - a local schedule row keeps its bare id (the 0.28 contract);
1487
+ - a workspace schedule row's is `<member>/<id>`.
1488
+ - `qualifiedId` is always the qualified form, and `name` is the bare id.
1489
+ - Every verb accepts `local/<id>` or a bare local id.
1490
+ - **Shared fields in every row:**
1491
+ - `origin`: where the item is defined, and where to open it:
1492
+ - `{ kind: "local", path: "oats-schedules.json", url: null, localPath }`;
1493
+ - `{ kind: "workspace", repoKey, path, commit, url, localPath }`: `url` is the
1494
+ file's web URL at `commit` (`https://github.com/<owner>/<repo>/blob/<commit>/<path>`
1495
+ for a `github.com` member, else `null`); `localPath` is the file in this
1496
+ machine's clone of the member (`null` when the member is not cloned here).
1497
+ - `description`, `owner`, `runsOn`;
1498
+ - `runsHere`; `reason` (`null` | `host-unnamed` | `assigned-elsewhere` | `owner-mismatch`) with `reasonDetail`;
1499
+ - `enabledHere`;
1500
+ - `soul`: `{ name, origin } | null` (`null` for a command, wake or operation schedule). `origin` is where the name resolves, per the snapshot:
1501
+ - `{ kind: "member", repoKey, member }`: a soul in a workspace member;
1502
+ - `{ kind: "package", package, version }`: a soul of a locked package;
1503
+ - `{ kind: "external", repoKey, source }`: an external soul (`source` is the workspace's `external[].source` ref);
1504
+ - `{ kind: "ambiguous", candidates }`: a bare name several souls answer to (`candidates` is how many); a spawn needs the qualified name;
1505
+ - `null`: not found (or no snapshot yet).
1506
+ - `task`: the template, verbatim;
1507
+ - `teams`, `launchConfig`, `harness`, `model`, `concurrency`;
1508
+ - `lastRun`, `nextDue`;
1509
+ - `invalid?: { code, message, field? }`.
1510
+ - **A trigger row** also carries `kind: "trigger"`, `on`, `spawn`, and `template?`:
1511
+ - `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);
1512
+ - `spawn: { soul, purpose, task, teams: [label], launchConfig?, harness?, model?, yolo?, backend? }`
1513
+ (`purpose` and `task` are templates over `{repo}`, `{number}`, `{url}`, `{event}`, `{trigger}`, `{headSha}`;
1514
+ `launchConfig` names a launch configuration in the running host's `oats-local.yaml`);
1515
+ - `template?: { package, version, commit, template }`: the package template it was added from.
1516
+ - `lastRun` is the last fired event: `{ at, instance, home, event, number, key }`.
1517
+ - `nextDue` is the next poll, only when it runs here; `null` before its first poll (it polls at the next tick).
1518
+ - **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`.
1519
+ - `nextDue` is the next minute, only when it runs here.
1520
+ - `teams` is `[]` and `concurrency` is `null`.
1521
+ - A workspace schedule another host runs carries its definition and placement only: `lastRun` and `nextDue` are `null`.
1522
+ - A spawn schedule's `launchConfig` names a launch configuration in the running host's `oats-local.yaml`.
1523
+ - **Naming:** `nextDue` is the one name for "when it next runs" in every trigger and
1524
+ schedule row. A schedule row still carries the 0.24 `nextRun` for older readers;
1525
+ they agree whenever it runs here.
1526
+ - **Actions:**
1527
+ - `enable` and `disable` on a workspace id edit `oats-local.yaml` `triggers.disabled` or `schedules.disabled`.
1528
+ - `update` and `remove` refuse it with `E_AUTOMATION_WORKSPACE { id, origin }`.
1529
+ - `schedule run` and `schedule reconcile` work when it runs here, else `E_AUTOMATION_NOT_HERE { id, reason, runsOn, owner }`.
1530
+ - **`oats trigger test <id>`** adds `placement: { runsOn, owner, host, runsHere, reason, detail?, enabledHere }`. Any reason, or disabled here, is a problem (`ok: false`).
1531
+ - **`oats schedule test <id> --json`** (local or workspace) → `{ test: { id, qualifiedId, kind,
1532
+ placement: { runsHere, reason, reasonDetail?, enabledHere, runsOn, owner, host },
1533
+ soul: { name, origin, resolves, error: { code, message } | null } | null, nextDue,
1534
+ spawned: false, problems: [string], ok } }`. `soul` is checked the way the run
1535
+ would start it (`oats spawn <soul> --preview`, which writes nothing); it is `null`
1536
+ for a command, wake or operation. `nextDue` is the next cron match whether or not
1537
+ this host runs it (`placement` says that). Not running here, disabled, invalid or a
1538
+ soul that does not resolve is a problem (`ok: false`). It spawns nothing and records
1539
+ nothing. Errors: `E_SCHEDULE_UNKNOWN`, `E_BAD_ARGS`.
1540
+ - **`oats trigger|schedule add … --workspace <member> --runs-on <host> --owner <host>/<login> --json`** answers `{ id, written, file: { member, repoKey, path, content, written? } }`.
1541
+ - Errors: `E_AUTOMATION_MEMBER` (not a confirmed member), `E_TRIGGER_EXISTS` or `E_SCHEDULE_EXISTS` (the file exists), and the kind's validation codes.
1542
+ - **`oats automations refresh --json`** answers `{ automationsApi, snapshot, triggers, schedules, problems: [...], takenAt }`.
1543
+ - **`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`).
1544
+ - **`oats workspace status --json`** gains `automations: { host, snapshot, rows: [{ kind, id, runsOn, owner, runsHere, reason, enabledHere, origin, invalid? }] }`.
1545
+ - **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: …" }`.
1546
+
1547
+ ### Desktop facts (feature `desktop-facts`, OATS 0.29.0)
1548
+
1549
+ These are facts the Workspace view shows. The kernel reports them so the
1550
+ Desktop never works them out itself. Gate reading every field below on
1551
+ `desktop-facts` in `features[]`. No API integer changes, and every field is an
1552
+ addition to an existing row.
1553
+
1554
+ **`oats inspect --soul <name> --json`: why each capability is there**
1555
+
1556
+ - `capabilities[].composedFrom` says which layer put the module in the soul:
1557
+ `"workspace"` (`defaults.<slot>` or `defaults.capabilities`),
1558
+ `"team:<label>"` (`defaults.byTeam.<label>.capabilities`) or `"soul"` (the
1559
+ soul's own `capabilities:`). This is the same vocabulary as
1560
+ `layers.<slot>.from`. It is `null` on `inspect --home`, because a spawn does
1561
+ not record it. `from` stays the module's origin object (`{kind, repoKey,
1562
+ commit}` or the package object), so it is a separate key.
1563
+ - `capabilitiesOff[]` lists the capabilities the soul turned off, which a
1564
+ lower layer would otherwise have given it. They are not rows of
1565
+ `capabilities[]`, because those are resolved modules with operations. Each
1566
+ entry is `{ id, off: true, from: "soul", reason, slot?, overrides }`:
1567
+ - `reason: "off"`: the soul wrote `<id>: off` over a workspace or team
1568
+ default.
1569
+ - `reason: "slot-none"`: the soul wrote `<slot>: none` (`slot` names it),
1570
+ which emptied the slot the workspace filled with `<id>`.
1571
+ - `overrides`: the layer whose default was turned off (`"workspace"` or
1572
+ `"team:<label>"`).
1573
+ - Sorted by id. `[]` on `inspect --home`.
1574
+
1575
+ **`oats souls --json` rows**
1576
+
1577
+ - `harness`, `model`, `harnessFrom`: what a spawn of the soul starts with
1578
+ when no `--harness`/`--model` is given. A v2 `soul.yaml` cannot declare a
1579
+ harness or a model, so today this is always `harness: "pi"`, `model: null`
1580
+ (the harness's native model) and `harnessFrom: "kernel-default"`.
1581
+ `harnessFrom: "soul"` is reserved for a schema that lets a soul declare
1582
+ one.
1583
+ - `spawnable`, `problem`: whether a spawn here would refuse.
1584
+ - `problem` is `{ code, message }` when a spawn would refuse, else `null`.
1585
+ - The kernel resolves the soul exactly as a spawn does, but spawns nothing,
1586
+ writes nothing and reads only the sync cache.
1587
+ - Codes: `E_SOUL_DISABLED` (this machine's `souls.disabled`),
1588
+ `E_TEAM_CONFLICT`, `E_CAPABILITY_MISSING`, `E_CAPABILITY_PRIVATE`,
1589
+ `E_CAPABILITY_INCOMPATIBLE`, `E_PACKAGE_MISSING`, `E_PACKAGE_INTEGRITY`,
1590
+ `E_LOCK_SCHEMA`, `E_REMOTE_*`, and any other resolution refusal.
1591
+ - An `E_TEAM_UNKNOWN` problem in `problems[]` is informational. It does not
1592
+ make a soul unspawnable.
1593
+ - `file`: `{ path, url }`, the soul's `soul.yaml` in its repository (see
1594
+ **URLs** at the end of this section).
1595
+
1596
+ **`oats capabilities --json` rows**
1597
+
1598
+ - `layer` on every row. Package rows now carry it too, from the package
1599
+ manifest; `null` for a capability outside the slots.
1600
+ - `description`: the manifest's `description`, or `null`.
1601
+ - `skills`, `commands`, `hooks`: what the capability provides, by name,
1602
+ sorted.
1603
+ - `skills` is enumerated as a spawn would. It is `null` when the declared
1604
+ skills cannot be listed, which a spawn of it would refuse.
1605
+ - `commands` and `hooks` are the keys of the manifest's `commands` and
1606
+ `hooks`.
1607
+ - `file`: `{ path, url }`, the capability's `oats.json`, or `null` when the
1608
+ manifest cannot be read.
1609
+ - `tree`: a member capability's fingerprint, the Git tree id of its
1610
+ directory at the member commit. The same bytes give the same id. It is
1611
+ `null` on package rows, whose fingerprint is `integrity` in the lock (see
1612
+ `oats workspace status`).
1613
+ - A package whose manifests cannot be read at its locked commit leaves these
1614
+ facts `null` on its rows.
1615
+ - Package manifests are read at the locked commit from the sync cache. There
1616
+ is no network beyond what `sync` already fetched.
1617
+
1618
+ **`oats workspace status --json`**
1619
+
1620
+ ```json
1621
+ {"workspace":{"…":"…","file":{"path":"oats-workspace.yaml","url":"https://github.com/acme/agents/blob/<oid>/oats-workspace.yaml"}},
1622
+ "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"}}],
1623
+ "packages":[{"id":"oats.okf","version":"3.0.0","source":"catalog:oats.okf","commit":"<oid>","…":"…","latest":{"version":"4.0.0","ref":"v4.0.0"}}],
1624
+ "defaults":{"slots":{"knowledge":{"name":"oats.okf","from":"package"},"messaging":"none","tasks":null},
1625
+ "capabilities":[{"name":"acme-house-style","from":"github.com/acme/agents","off":false}],
1626
+ "byTeam":{"engineering":{"capabilities":[{"name":"acme-house-style","from":null,"off":true},{"name":"acme-deploy","from":"package","off":false}]}}},
1627
+ "clones":[{"key":"github.com/acme/agents","name":"agents","path":"/abs/acme-workspace/agents","rule":"convention"},
1628
+ {"key":"github.com/acme/tools","name":"tools","path":null,"rule":null}],
1629
+ "disabledSouls":["release-reviewer"],
1630
+ "lock":{"path":"/abs/acme-workspace/oats-lock.json","lockfileVersion":3}}
1631
+ ```
1632
+
1633
+ - `defaults`: the workspace file's defaults, as declared rather than
1634
+ resolved for a soul.
1635
+ - `slots.<slot>` is `{ name, from }` when the workspace fills it, `"none"`
1636
+ when it empties it, and `null` when it says nothing.
1637
+ - `capabilities` and `byTeam.<label>.capabilities` are rows `{ name, from,
1638
+ off }`, sorted by name. `from` is the declared location (`"package"`,
1639
+ `"here"` or a member repo key); an `off` row has `from: null`.
1640
+ - Standalone: slots `null`, `capabilities: []` and `byTeam: {}`.
1641
+ - `clones`: this computer's clone of each member.
1642
+ - `path` is the absolute clone, or `null` when this machine has none.
1643
+ - `rule` names what found it: `"clones"` (the `oats-local.yaml` `clones:`
1644
+ entry) or `"convention"` (`<deployment>/<member name>`). It is `null`
1645
+ with no clone.
1646
+ - A path that is not the member's clone gives `path: null, rule: null,
1647
+ problem: { code: "E_CLONE_MISMATCH", message }`, the refusal a spawn
1648
+ would meet.
1649
+ - (`--repo` is a spawn option, so it plays no part here.)
1650
+ - `disabledSouls`: `oats-local.yaml` `souls.disabled`, as written.
1651
+ - `lock`: `{ path, lockfileVersion }`. The per-package commit is each
1652
+ `packages[]` row's `commit`, and its fingerprint is `integrity`.
1653
+ - `packages[].latest`: `{ version, ref }` when the official catalog shipped
1654
+ with this kernel has a newer version of a catalog-sourced package than the
1655
+ lock holds. It is `null` when the pin is current and for `git:` packages.
1656
+ It never reaches the network: the catalog is the kernel's own
1657
+ (`OATS_PACKAGE_CATALOG` overrides it, as for `sync`).
1658
+ - `workspace.file` is `{ path, url }` for the workspace file in the
1659
+ workspace repository (at `workspace.key` @ `workspace.commit`). It is
1660
+ `null` for a standalone deployment.
1661
+ - `members[].url` is the member repository at its commit.
1662
+ `members[].membershipFile` is `{ path, url }`.
1663
+
1664
+ **`oats status --json` instance rows**
1665
+
1666
+ - A member module's `modules[].current` gains `version` (the capability's
1667
+ manifest version at the current commit, `null` when it has none) beside
1668
+ `commit`, on `current` and `moved` rows. Package rows already carried it. On a `moved` row, the recorded `commit`/`from` and `current`
1669
+ together say what moved and to what.
1670
+ - `startedAt`: the last session start or restart (the session receipt). A
1671
+ home spawned with a launch and never restarted uses `createdAt`. A home
1672
+ never launched is `null`. `createdAt` stays the spawn time.
1673
+ - `modelFrom`: where the model the home runs came from.
1674
+ - `"soul"`: the soul's model preference.
1675
+ - `"spawn"` or `"start"`: an explicit `--model` on that command.
1676
+ - `"launch-config"`: a launch configuration's model.
1677
+ - `"harness-default"`: the harness's own model.
1678
+ - A start that reuses the recorded model keeps the recorded answer.
1679
+ - `null` for a home spawned before 0.29.0. `instance.json` records it as
1680
+ `modelFrom`.
1681
+ - `identityAddress`: the messaging identity's `address` (else `alias`) that
1682
+ the messaging capability recorded (`capabilityMeta.<messaging>.identity`),
1683
+ passed through unchanged. `null` otherwise.
1684
+
1685
+ **URLs.** Every `url` is a browsable page of
1686
+ the file (or of the repository, for a member) at the commit the row names.
1687
+ Only repositories on `github.com` have one (`https://github.com/<org>/<repo>/blob/<commit>/<path>`,
1688
+ or `/tree/<commit>`). Every other host and local repository gives `url:
1689
+ null`, with `path` still set. `path` is relative to that repository's root.
1690
+
1691
+ **Help.** `oats help` lists `spawn … [--provider <capability> <key>=<value>]`.
1692
+
1431
1693
  ### Eligible teams (feature `teams`, OATS 0.26.0)
1432
1694
 
1433
1695
  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
@@ -57,8 +57,14 @@ source. The current authoring-reference patch is package 1.0.1: once framework
57
57
  v0.23.1 is published, an explicit initial Git acquisition at that tag selects
58
58
  the patch instead. It does not silently change the catalog's 1.0.0 selection
59
59
  or an existing lock. For local development, use an explicit complete source
60
- package path instead. Activation exposes the expert and targets
61
- the authoring skill, without selecting or replacing a knowledge capability.
60
+ package path instead. Activation targets the authoring skill, without
61
+ selecting or replacing a knowledge capability.
62
+
63
+ The expert is an oats.framework **package soul** from framework 1.3.0
64
+ (`oats-package/souls/knowledge-theory-expert/`, reading `oats.knowledge-theory`
65
+ 1.1.0 from its own package): spawn it by its qualified name in the author's
66
+ repository, `oats spawn oats.framework/knowledge-theory-expert --repo <repo>`.
67
+ Before 1.3.0 it was a capability-defined agent, which OATS 0.29.0 removed.
62
68
  There are no executable surfaces to trust in this package. Installed experts
63
69
  use their materialized local curriculum, not this repository at runtime.
64
70
 
@@ -35,8 +35,7 @@ A knowledge implementation's capability manifest might begin:
35
35
  "compatibility": { "oats": ">=0.22.19" },
36
36
  "layer": "knowledge",
37
37
  "skills": ["skills/native-reader", "skills/native-harvest"],
38
- "inject": "injects/knowledge.md",
39
- "agents": ["agents/native-harvester"]
38
+ "inject": "injects/knowledge.md"
40
39
  }
41
40
  ```
42
41
 
@@ -49,14 +48,18 @@ kernel's real manifest validation, not this example as a complete schema.
49
48
 
50
49
  The optional `oats.knowledge-theory` capability is deliberately **different**:
51
50
  it is additive, declares no layer, injection, command or hook, and supplies
52
- only an expert and its authoring skill. It neither selects knowledge policy nor
51
+ only its authoring skill; the expert that uses it is the oats.framework package
52
+ soul `knowledge-theory-expert`. It neither selects knowledge policy nor
53
53
  depends on OKF. A runtime integration should not depend on it just to inherit
54
54
  mandatory doctrine. Explicit versioned reuse is a choice, not a requirement.
55
55
 
56
56
  ## Soul craft
57
57
 
58
- A capability's `agents/<name>/` contains `soul.yaml`, canonical `AGENTS.md` and
59
- relative `CLAUDE.md -> AGENTS.md`. Keep role instructions to a screen or two:
58
+ An agent a package ships is a **package soul**: `souls/<name>/` beside the
59
+ package's capabilities (listed in `oats-package.json` `souls:`), holding
60
+ `soul.yaml`, canonical `AGENTS.md` and relative `CLAUDE.md -> AGENTS.md`; it
61
+ reads the package's own capabilities with `from: here`. (A capability manifest
62
+ declares no agents: `agents:` was removed in OATS 0.29.0.) Keep role instructions to a screen or two:
60
63
  role and boundaries, operating loop, verification, local skill pointer,
61
64
  escalation. Do not bury an entire curriculum in always-loaded instructions.
62
65
 
package/docs/knowledge.md CHANGED
@@ -311,6 +311,107 @@ receipts. Inputs are processed only when required destinations resolve. All-drop
311
311
  or no-change judgment can be successful without inventing a PR. Enqueue, worker
312
312
  spawn and command exit alone are not successful learning.
313
313
 
314
+ ## Knowledge operations
315
+
316
+ > **Version scope:** oats.okf **4.0.0** on kernel **0.29.0** (package souls,
317
+ > triggers and workspace automations). Everything above describes the 2.x runtime, which 4.0.0 keeps for
318
+ > capture, custody and delivery. The design and its decisions are in
319
+ > [the knowledge-operations plan](design/2026-09-26-okf-knowledge-operations.md).
320
+ > The setup procedure is the `oats-onboarding` skill ("Knowledge operations with
321
+ > OKF") and okf's `okf-trigger-setup`.
322
+
323
+ From 4.0.0, harvested knowledge is judged by a harvester, reviewed by a
324
+ maintainer and merged without an operator in the loop, except where a merge
325
+ would supersede a human-accepted decision.
326
+
327
+ ### The flow
328
+
329
+ 1. **Capture.** A working soul whose knowledge slot is `oats.okf`, on a host
330
+ where harvest is on (below), registers a source at spawn. Capture and
331
+ custody are as above: notes and bounded transcript windows, copied outside
332
+ the home.
333
+ 2. **Harvest.** The source's `run-source` job spawns the package soul
334
+ `oats.okf/knowledge-harvester` (team `okf`). It reads the input in full,
335
+ transcript windows included, and judges it with the OKF promotion
336
+ doctrine. It stages edits on the owned nodes and opens a PR on the
337
+ knowledge-base repo, labelled `okf-harvest`, whose body carries a fenced
338
+ `okf-harvest` provenance block (the run, the source soul and instance, the
339
+ owned and read nodes, the task references, the harvester's alias). It
340
+ stays alive, answering questions in `okf`, until the PR is merged or
341
+ closed, then retires. `harvester-max-age` (default 7d) bounds it; it never
342
+ closes its own PR.
343
+ 3. **Trigger.** The workspace declares the trigger in a member repo,
344
+ `oats-triggers/okf-harvest-review.yaml` (`kind: oats-trigger`), from the package template
345
+ `oats.okf:harvest-review`. It names the host that runs it (`runsOn`, that
346
+ machine's `host.name`) and the GitHub account it acts as (`owner`, which
347
+ must be able to merge on the knowledge-base repo). Only that host, logged
348
+ in to `gh` as that account, polls for such PRs. For each one it spawns a
349
+ NEW `oats.okf/knowledge-maintainer`, joining `okf`. The event reaches it as
350
+ `OATS_TRIGGER_EVENT_FILE`. A local trigger (`oats trigger add`, this host
351
+ only) is the machine-private alternative. Triggers are described in
352
+ [schedules.md, "Triggers"](schedules.md#triggers), and the workspace
353
+ file in ["Workspace triggers and schedules"](schedules.md#workspace-triggers-and-schedules).
354
+ 4. **Review.** The maintainer checks out the PR and situates it: the
355
+ provenance, the source soul's owned and read nodes, the neighbouring
356
+ concepts, and the source's tickets when a tasks capability can read them.
357
+ It records a verdict on the PR (`merge`, `amend+merge`, `request-changes`
358
+ or `close`), amends what needs amending, and merges with the host's `gh`. A
359
+ PR that would supersede a concept with human acceptance evidence is not
360
+ merged: it is labelled `okf-needs-human` for the workspace's human. The
361
+ maintainer tells the harvester the outcome and retires.
362
+
363
+ ### Who gets which okf skills
364
+
365
+ | Capability | Composed into | Skills | Inject |
366
+ |---|---|---|---|
367
+ | `oats.okf` | every working soul whose knowledge slot it fills | `okf-consultation` (reading soul knowledge and citing it); `okf-instance-knowledge` (what instance knowledge is worth capturing, and the form of `STATE.md`, `log.md` and `notes/`) | the work mode: consult instance memory and soul knowledge at task start, after compaction and before decisions; capture before compaction |
368
+ | `oats.okf-harvest` | `oats.okf/knowledge-harvester` only | `knowledge-theory` (the OKF promotion doctrine); `knowledge-harvest` (the procedure, through the PR's lifetime); `okf-authoring` | the harvester's: a judge, not a worker; the staged roots are its only write surface |
369
+ | `oats.okf-maintenance` | `oats.okf/knowledge-maintainer` only | `knowledge-theory`; `knowledge-review`; `okf-authoring`; `okf-trigger-setup` | the maintainer's: one PR per instance; supersede explicitly, never silently |
370
+
371
+ Working souls get no promotion doctrine: the harvester is the only judge of
372
+ what is promoted, and the maintainer the only one who merges. The shared
373
+ skills ship as identical copies in each capability. The harvester and the
374
+ maintainer hold no knowledge slot, so nothing harvests them.
375
+
376
+ ### The harvest switch
377
+
378
+ Harvest is off unless both the host and the soul allow it:
379
+
380
+ | Where | Setting | Effect |
381
+ |---|---|---|
382
+ | The host, `oats-local.yaml` | `settings.oats.okf.harvest: on` (default `off`) | This host harvests its working souls. |
383
+ | A soul, `soul.yaml` | `knowledge: { harvest: off }` | This soul is never harvested, whatever the host says. |
384
+
385
+ Off means **no capture at all**: no source is registered and no transcript or
386
+ notes enter custody, so nothing accumulates for later. Turning it on starts
387
+ with the next session. `oats okf setup --harvest on|off` writes the host
388
+ setting, and `oats okf harvest-status [--soul <soul>]` reports the effective
389
+ value, the row that decided it and the registered sources.
390
+ `oats schedule disable <job>` on a source's `run-source` job is an emergency
391
+ brake for one source, not the switch. The review trigger does not depend on the
392
+ switch: a host can review harvest PRs from other hosts without harvesting.
393
+ Keep harvest off until the end-to-end check in `okf-trigger-setup` passes.
394
+
395
+ ### The `okf` team
396
+
397
+ The package souls carry `team: okf`. The workspace declares the label and maps
398
+ it to a messaging team:
399
+
400
+ ```yaml
401
+ teams:
402
+ okf: { description: Knowledge operations }
403
+ messaging:
404
+ byTeam:
405
+ okf: { team: <messaging team id> }
406
+ ```
407
+
408
+ Harvesters and maintainers talk there (subjects prefixed `okf:` with the PR's
409
+ URL) without writing into the working teams. Like every label it organises and
410
+ gates nothing. A workspace without it reports `E_TEAM_UNKNOWN` on both package
411
+ souls in discovery. They still spawn, but into no messaging team, so the
412
+ harvester and the maintainer cannot talk, and `oats trigger test` fails its
413
+ team check.
414
+
314
415
  ## Inspection and operator commands
315
416
 
316
417
  Run home-local commands from that source home: inside an instance the
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://oats.dev/schemas/local-v2.json",
4
4
  "title": "Deployment-local overrides v2 (oats-local.yaml)",
5
- "description": "The operator's side of a workspace: which workspace this machine realizes, where member clones live when not at the taught convention, host-owned capability settings (absolute paths belong HERE, never in the workspace file), souls disabled on this machine, and the named launch configurations this host offers. Never shared through Git.",
5
+ "description": "The operator's side of a workspace: which workspace this machine realizes, where member clones live when not at the taught convention, host-owned capability settings (absolute paths belong HERE, never in the workspace file), souls disabled on this machine, this host's name and the workspace triggers and schedules it does not run, and the named launch configurations this host offers. Never shared through Git.",
6
6
  "type": "object",
7
7
  "required": ["schemaVersion", "workspace"],
8
8
  "additionalProperties": false,
@@ -64,6 +64,36 @@
64
64
  }
65
65
  }
66
66
  },
67
+ "host": {
68
+ "type": "object",
69
+ "additionalProperties": false,
70
+ "required": ["name"],
71
+ "properties": {
72
+ "name": { "type": "string", "pattern": "^[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$", "description": "This machine's name (0.29.0). A workspace trigger or schedule runs on the host whose name is its `runsOn` (and only when this host's gh account is its `owner`). A machine fact, never in Git." }
73
+ }
74
+ },
75
+ "triggers": {
76
+ "type": "object",
77
+ "additionalProperties": false,
78
+ "properties": {
79
+ "disabled": {
80
+ "description": "Workspace triggers this host does not run, by qualified id <member>/<id>, without a commit (`oats trigger disable <member>/<id>` writes it). Local triggers are enabled and disabled in oats-schedules.json.",
81
+ "type": "array", "uniqueItems": true,
82
+ "items": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*/[a-z0-9-]{1,40}$" }
83
+ }
84
+ }
85
+ },
86
+ "schedules": {
87
+ "type": "object",
88
+ "additionalProperties": false,
89
+ "properties": {
90
+ "disabled": {
91
+ "description": "Workspace schedules this host does not run, by qualified id <member>/<id>, without a commit (`oats schedule disable <member>/<id>` writes it). Local schedules are enabled and disabled in oats-schedules.json.",
92
+ "type": "array", "uniqueItems": true,
93
+ "items": { "type": "string", "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]*/[a-z0-9-]{1,40}$" }
94
+ }
95
+ }
96
+ },
67
97
  "souls": {
68
98
  "type": "object",
69
99
  "additionalProperties": false,
@@ -65,13 +65,16 @@ this policy does not invent new catalog or manifest fields.
65
65
 
66
66
  ## Listed first set
67
67
 
68
- - Listed capabilities: `oats.okf`, `oats.aweb`, `oats.authoring`, `oats.jira`,
69
- `oats.linear`, `oats.dev`, `oats.knowledge-theory`, `oats.core` and `oats.setup`.
70
- - **`oats.framework` 1.1.3** is listed at tag `oats-framework/v1.1.3` in
68
+ - Listed capabilities: `oats.okf`, `oats.okf-harvest`, `oats.okf-maintenance`,
69
+ `oats.aweb`, `oats.authoring`, `oats.jira`, `oats.linear`, `oats.dev`,
70
+ `oats.knowledge-theory`, `oats.core` and `oats.setup`. `oats.okf-harvest` and
71
+ `oats.okf-maintenance` select the `oats.okf` package (4.0.0).
72
+ - **`oats.framework` 1.3.0** is listed at tag `oats-framework/v1.3.0` in
71
73
  `awebai/oats`, payload root `oats-package`. The `oats.core`, `oats.setup` and
72
74
  `oats.knowledge-theory` aliases select that distribution; package identity is
73
75
  distinct from capability identity. Core supplies operation/soul guidance;
74
- setup supplies OATS Soul Setup, configuration and package guidance.
76
+ setup supplies OATS Soul Setup, configuration and package guidance. The
77
+ package also ships the `knowledge-theory-expert` package soul.
75
78
 
76
79
  These entries are in the current repository catalog. An older installed CLI keeps
77
80
  its bundled catalog; publication here does not update that installation or rewrite