@awebai/oats 0.29.4 → 0.30.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 (224) hide show
  1. package/README.md +12 -6
  2. package/bin/oats.mjs +194 -50
  3. package/capabilities/oats-aweb/bin/oats-aweb.mjs +538 -204
  4. package/capabilities/oats-aweb/injects/aweb.md +1 -1
  5. package/capabilities/oats-aweb/lib/binding-wire.mjs +31 -22
  6. package/capabilities/oats-aweb/oats.json +5 -12
  7. package/capabilities/oats-aweb/skills/VENDORED.md +4 -4
  8. package/capabilities/oats-aweb/skills/aweb-team-membership/SKILL.md +1 -1
  9. package/capabilities/oats-aweb/skills/oats-aweb/SKILL.md +83 -13
  10. package/capabilities/oats-code-review/injects/reviewer.md +26 -0
  11. package/capabilities/oats-code-review/oats.json +16 -0
  12. package/capabilities/oats-code-review/skills/adversarial-review/SKILL.md +66 -0
  13. package/capabilities/oats-code-review/skills/review-dev-docs/SKILL.md +30 -0
  14. package/capabilities/oats-code-review/skills/security-review/SKILL.md +56 -0
  15. package/capabilities/oats-code-review/skills/simplification-review/SKILL.md +34 -0
  16. package/capabilities/oats-developer/injects/developer.md +38 -0
  17. package/capabilities/oats-developer/oats.json +17 -0
  18. package/capabilities/oats-developer/skills/execution-strategy/SKILL.md +43 -0
  19. package/capabilities/oats-developer/skills/maintain-dev-docs/SKILL.md +47 -0
  20. package/capabilities/oats-developer/skills/run-the-review-loop/SKILL.md +65 -0
  21. package/capabilities/oats-developer/skills/understand-the-spec/SKILL.md +37 -0
  22. package/capabilities/oats-developer/skills/worktrees/SKILL.md +36 -0
  23. package/capabilities/oats-engineering-expert/injects/expert.md +37 -0
  24. package/capabilities/oats-engineering-expert/oats.json +17 -0
  25. package/capabilities/oats-engineering-expert/skills/coordinate-developers/SKILL.md +37 -0
  26. package/capabilities/oats-engineering-expert/skills/coordinate-experts/SKILL.md +52 -0
  27. package/capabilities/oats-engineering-expert/skills/land-your-prs/SKILL.md +50 -0
  28. package/capabilities/oats-engineering-expert/skills/plan-and-spec/SKILL.md +53 -0
  29. package/capabilities/oats-engineering-expert/skills/verify-developer-work/SKILL.md +49 -0
  30. package/capabilities/oats-okf/bin/oats-okf.mjs +8 -4
  31. package/capabilities/oats-okf/lib/binding-wire.mjs +47 -15
  32. package/capabilities/oats-okf/lib/inspection.mjs +26 -7
  33. package/capabilities/oats-okf/lib/sources.mjs +16 -2
  34. package/capabilities/oats-okf/lib/worker.mjs +5 -16
  35. package/capabilities/oats-okf/oats.json +6 -3
  36. package/capabilities/oats-okf-harvest/bin/okf-harvest.mjs +2 -2
  37. package/capabilities/oats-okf-harvest/oats.json +3 -3
  38. package/capabilities/oats-okf-harvest/skills/knowledge-harvest/SKILL.md +6 -6
  39. package/capabilities/oats-okf-maintenance/bin/okf-maintenance.mjs +2 -2
  40. package/capabilities/oats-okf-maintenance/injects/maintainer.md +1 -1
  41. package/capabilities/oats-okf-maintenance/oats.json +2 -2
  42. package/capabilities/oats-okf-maintenance/skills/knowledge-review/SKILL.md +1 -1
  43. package/capabilities/oats-okf-maintenance/skills/okf-trigger-setup/SKILL.md +11 -23
  44. package/capabilities/oats-workspace-experts/injects/oats-experts.md +26 -0
  45. package/capabilities/oats-workspace-experts/oats.json +9 -0
  46. package/docs/capabilities.md +160 -171
  47. package/docs/capability-manifest.schema.json +6 -11
  48. package/docs/configuration.md +213 -64
  49. package/docs/design/2026-09-16-knowledge-capability-contract.md +36 -50
  50. package/docs/design/2026-09-23-workspace-module-contracts.md +377 -544
  51. package/docs/design/2026-09-26-okf-knowledge-operations.md +132 -357
  52. package/docs/design/2026-09-27-team-model-v2.md +97 -117
  53. package/docs/design/2026-09-28-automations-trust.md +38 -0
  54. package/docs/design/2026-09-28-soul-launch-preference.md +63 -0
  55. package/docs/design/HISTORY.md +65 -0
  56. package/docs/design/README.md +23 -54
  57. package/docs/desktop-cli-api.md +1787 -1777
  58. package/docs/desktop.md +30 -91
  59. package/docs/execution-targets.md +146 -292
  60. package/docs/first-team.md +31 -17
  61. package/docs/implementation.md +76 -288
  62. package/docs/integrations.md +118 -320
  63. package/docs/knowledge-capability-authoring.md +25 -52
  64. package/docs/knowledge-reference/acceptance.md +3 -3
  65. package/docs/knowledge-reference/adoption.md +1 -1
  66. package/docs/knowledge-reference/harvester.md +2 -2
  67. package/docs/knowledge-reference/package-craft.md +3 -3
  68. package/docs/knowledge-reference/provider-mapping.md +3 -6
  69. package/docs/knowledge-reference/reader-capture.md +3 -3
  70. package/docs/knowledge-theory.md +62 -166
  71. package/docs/knowledge.md +225 -404
  72. package/docs/layers.md +42 -97
  73. package/docs/oats-local.schema.json +58 -5
  74. package/docs/oats-membership.schema.json +1 -8
  75. package/docs/oats-package.schema.json +5 -5
  76. package/docs/oats-workspace.schema.json +8 -22
  77. package/docs/official-catalog.md +25 -28
  78. package/docs/packages.md +45 -63
  79. package/docs/plans/0.30-close-out.md +61 -0
  80. package/docs/release-lane.md +77 -0
  81. package/docs/release-notes/oats-framework-v1.1.3.md +10 -8
  82. package/docs/release-notes/v0.19.0.md +48 -147
  83. package/docs/release-notes/v0.19.1.md +2 -3
  84. package/docs/release-notes/v0.19.3.md +2 -15
  85. package/docs/release-notes/v0.20.0.md +0 -15
  86. package/docs/release-notes/v0.22.0.md +71 -138
  87. package/docs/release-notes/v0.22.1.md +42 -90
  88. package/docs/release-notes/v0.22.10.md +1 -1
  89. package/docs/release-notes/v0.22.11.md +1 -47
  90. package/docs/release-notes/v0.22.12.md +4 -13
  91. package/docs/release-notes/v0.22.13.md +1 -42
  92. package/docs/release-notes/v0.22.14.md +3 -11
  93. package/docs/release-notes/v0.22.15.md +1 -46
  94. package/docs/release-notes/v0.22.16.md +6 -8
  95. package/docs/release-notes/v0.22.18.md +1 -99
  96. package/docs/release-notes/v0.22.19.md +3 -14
  97. package/docs/release-notes/v0.22.2.md +6 -15
  98. package/docs/release-notes/v0.22.3.md +0 -1
  99. package/docs/release-notes/v0.22.4.md +1 -14
  100. package/docs/release-notes/v0.22.5.md +2 -12
  101. package/docs/release-notes/v0.22.6.md +0 -3
  102. package/docs/release-notes/v0.23.0.md +9 -25
  103. package/docs/release-notes/v0.23.1.md +9 -25
  104. package/docs/release-notes/v0.23.2.md +2 -4
  105. package/docs/release-notes/v0.24.0.md +56 -97
  106. package/docs/release-notes/v0.24.1.md +7 -11
  107. package/docs/release-notes/v0.24.10.md +34 -45
  108. package/docs/release-notes/v0.24.11.md +12 -20
  109. package/docs/release-notes/v0.24.12.md +35 -48
  110. package/docs/release-notes/v0.24.13.md +34 -41
  111. package/docs/release-notes/v0.24.2.md +9 -13
  112. package/docs/release-notes/v0.24.3.md +7 -11
  113. package/docs/release-notes/v0.24.4.md +6 -6
  114. package/docs/release-notes/v0.24.5.md +6 -10
  115. package/docs/release-notes/v0.24.6.md +2 -5
  116. package/docs/release-notes/v0.24.7.md +46 -75
  117. package/docs/release-notes/v0.24.8.md +58 -96
  118. package/docs/release-notes/v0.24.9.md +38 -54
  119. package/docs/release-notes/v0.25.0.md +59 -76
  120. package/docs/release-notes/v0.25.1.md +57 -81
  121. package/docs/release-notes/v0.25.2.md +51 -70
  122. package/docs/release-notes/v0.25.3.md +11 -13
  123. package/docs/release-notes/v0.25.4.md +9 -13
  124. package/docs/release-notes/v0.25.5.md +3 -5
  125. package/docs/release-notes/v0.25.6.md +20 -29
  126. package/docs/release-notes/v0.25.7.md +5 -7
  127. package/docs/release-notes/v0.25.8.md +26 -39
  128. package/docs/release-notes/v0.26.0.md +175 -646
  129. package/docs/release-notes/v0.27.0.md +4 -5
  130. package/docs/release-notes/v0.27.1.md +4 -6
  131. package/docs/release-notes/v0.27.2.md +1 -1
  132. package/docs/release-notes/v0.28.0.md +57 -124
  133. package/docs/release-notes/v0.29.0.md +89 -208
  134. package/docs/release-notes/v0.29.1.md +1 -1
  135. package/docs/release-notes/v0.29.2.md +3 -4
  136. package/docs/release-notes/v0.30.0.md +205 -0
  137. package/docs/schedules.md +280 -363
  138. package/docs/servers.md +99 -117
  139. package/docs/soul.schema.json +2 -9
  140. package/docs/souls-and-instances.md +145 -158
  141. package/docs/workspaces.md +132 -215
  142. package/lib/automations.mjs +21 -6
  143. package/lib/core.mjs +226 -74
  144. package/lib/instance-events.mjs +1 -1
  145. package/lib/instance-inspect.mjs +109 -34
  146. package/lib/instance-lifecycle.mjs +14 -1
  147. package/lib/instance-resolution.mjs +26 -27
  148. package/lib/launch-preference.mjs +87 -0
  149. package/lib/materialize.mjs +3 -3
  150. package/lib/resolve.mjs +29 -87
  151. package/lib/schedule.mjs +1 -1
  152. package/lib/teams-verbs.mjs +195 -0
  153. package/lib/teams.mjs +190 -0
  154. package/lib/triggers.mjs +2 -2
  155. package/lib/workspace.mjs +54 -147
  156. package/package-catalog.json +9 -15
  157. package/package.json +1 -1
  158. package/skills/oats-getting-started/SKILL.md +25 -13
  159. package/capabilities/oats-review/injects/review.md +0 -69
  160. package/capabilities/oats-review/oats.json +0 -10
  161. package/capabilities/oats-review/skills/code-review/SKILL.md +0 -44
  162. package/capabilities/oats-review/skills/security-review/SKILL.md +0 -59
  163. package/docs/conventions.md +0 -90
  164. package/docs/design/2026-09-07-architecture-reassessment.md +0 -131
  165. package/docs/design/2026-09-07-desktop-souls-capabilities.md +0 -50
  166. package/docs/design/2026-09-07-mobile-agent-management-proposal.md +0 -228
  167. package/docs/design/2026-09-08-expert-assisted-deployment-proposal.md +0 -558
  168. package/docs/design/2026-09-13-knowledge-and-memory-direction.md +0 -744
  169. package/docs/design/2026-09-13-knowledge-implementation.md +0 -127
  170. package/docs/design/2026-09-13-knowledge-location-contract.md +0 -340
  171. package/docs/design/2026-09-14-artifact-retention-contract.md +0 -190
  172. package/docs/design/2026-09-14-portable-souls-and-git-workspaces.md +0 -708
  173. package/docs/design/2026-09-14-portable-souls-contract-amendments.md +0 -85
  174. package/docs/design/2026-09-14-portable-souls-explainer.md +0 -750
  175. package/docs/design/2026-09-15-captured-dispatch.md +0 -127
  176. package/docs/design/2026-09-15-captured-resolution-records.md +0 -143
  177. package/docs/design/2026-09-15-package-preparation.md +0 -100
  178. package/docs/design/2026-09-15-portable-data-contract.md +0 -121
  179. package/docs/design/2026-09-15-portable-declarations.md +0 -189
  180. package/docs/design/2026-09-15-portable-souls-handoff.md +0 -150
  181. package/docs/design/2026-09-15-portable-souls-implementation.md +0 -417
  182. package/docs/design/2026-09-15-selection-lock-and-approval.md +0 -122
  183. package/docs/design/2026-09-15-source-observation.md +0 -119
  184. package/docs/design/2026-09-16-captured-admission.md +0 -77
  185. package/docs/design/2026-09-16-captured-helper-dispatch.md +0 -105
  186. package/docs/design/2026-09-16-captured-launch-inputs.md +0 -42
  187. package/docs/design/2026-09-16-command-profile-preparation.md +0 -86
  188. package/docs/design/2026-09-16-fresh-install-first-rollout.md +0 -47
  189. package/docs/design/2026-09-16-fresh-operator-walkthrough.md +0 -282
  190. package/docs/design/2026-09-16-messaging-capability-contract.md +0 -59
  191. package/docs/design/2026-09-16-portable-migration-evidence.md +0 -158
  192. package/docs/design/2026-09-16-portable-onboarding.md +0 -179
  193. package/docs/design/2026-09-16-prepare-request-transport.md +0 -26
  194. package/docs/design/2026-09-16-provider-binding-codecs.md +0 -98
  195. package/docs/design/2026-09-16-provider-binding-wire.md +0 -274
  196. package/docs/design/2026-09-17-capability-helper-input-contract.md +0 -95
  197. package/docs/design/2026-09-17-captured-backend-parity.md +0 -53
  198. package/docs/design/2026-09-17-captured-native-start.md +0 -58
  199. package/docs/design/2026-09-17-portable-boundary-hookup.md +0 -19
  200. package/docs/design/2026-09-17-portable-boundary-resources.md +0 -52
  201. package/docs/design/2026-09-17-public-captured-start.md +0 -108
  202. package/docs/design/2026-09-17-public-prepare-request.md +0 -90
  203. package/docs/design/2026-09-18-captured-pi-host.md +0 -205
  204. package/docs/design/2026-09-18-first-cut-release-checklist.md +0 -131
  205. package/docs/design/2026-09-18-herdr-protocol-compatibility.md +0 -60
  206. package/docs/design/2026-09-20-redesign-program-board.md +0 -142
  207. package/docs/design/2026-09-20-workspace-and-portable-adoption-plan.md +0 -289
  208. package/docs/design/2026-09-20-workspace-onboarding-public.md +0 -207
  209. package/docs/design/2026-09-22-desktop-parity-seams.md +0 -58
  210. package/docs/design/2026-09-23-simplified-workspace-model.md +0 -711
  211. package/docs/design/2026-09-23-workspace-v2-implementation-plan.md +0 -65
  212. package/docs/design/2026-09-24-desktop-phase-f-boundary.md +0 -242
  213. package/docs/design/2026-09-24-phase-d-plan.md +0 -305
  214. package/docs/design/2026-09-25-teams-contract.md +0 -258
  215. package/docs/design/2026-09-26-desktop-design-brief-architecture.md +0 -241
  216. package/docs/design/desktop-ux-plan.md +0 -362
  217. package/docs/design/launch-configurations.md +0 -168
  218. package/docs/design/okf-mirror-provenance.md +0 -105
  219. package/docs/design/operations-contract.md +0 -141
  220. package/docs/oats-member.schema.json +0 -38
  221. package/skills/integration-authoring/SKILL.md +0 -84
  222. package/skills/oats-support/SKILL.md +0 -79
  223. package/skills/skill-craft/SKILL.md +0 -109
  224. package/skills/soul-craft/SKILL.md +0 -116
package/docs/schedules.md CHANGED
@@ -1,120 +1,102 @@
1
1
  # Schedules
2
2
 
3
3
  A schedule launches an agent, runs an oats command, or wakes an existing
4
- instance on a cron. Definitions belong to a scope and are committable; every
5
- `oats schedule` command run anywhere inside that scope, including from an
6
- instance home, reads and writes the same file. The scope is the deployment
7
- directory ([workspaces.md](workspaces.md)) — the one holding `oats-local.yaml`
8
- and the `agents/` root, found walking up; with none in reach, `oats schedule`
9
- is `E_LOCAL_MISSING`. Scheduled spawns materialize exactly like `oats spawn`.
10
- Execution belongs to the host that holds the scope, so a schedule on a
11
- registered server keeps running while your laptop sleeps.
12
-
13
- [Triggers](#triggers) are evaluated by the same tick. There is no daemon. One host timer (a launchd user agent on macOS, a systemd
14
- user timer on Linux) runs `oats schedule tick --host` once a minute; the tick
15
- is a short-lived process that evaluates only the current minute, launches
16
- what is due through the same `spawn`, `session start` and `session input`
17
- paths you use by hand, records what it observed, and exits. Minutes missed
18
- while the machine slept are skipped, never replayed. There are no retries
19
- and no queue.
4
+ instance on a cron. A [trigger](#triggers) spawns an agent when a GitHub pull
5
+ request event matches. Both are defined at one of two levels:
6
+
7
+ - **In the workspace**: a YAML file committed in a member repository, shared
8
+ through Git, addressed `<member>/<id>`, and run only on the host its
9
+ `runsOn` names. See [Workspace triggers and schedules](#workspace-triggers-and-schedules).
10
+ - **Locally**: in `<deployment>/oats-schedules.json`, addressed `local/<id>`
11
+ (or the bare `<id>`). This file belongs to one machine, like the
12
+ `oats-local.yaml` beside it: its definitions name absolute paths on that
13
+ machine, and only that machine runs them.
14
+
15
+ The deployment is the directory holding `oats-local.yaml`, found walking up
16
+ from the current directory or `--dir` ([configuration.md](configuration.md#the-deployment-directory));
17
+ with none in reach, the commands answer `E_LOCAL_MISSING`. Every command run
18
+ inside the deployment, including from an instance home, sees the same
19
+ definitions.
20
+
21
+ There is no daemon. One host timer (a launchd user agent on macOS, a systemd
22
+ user timer on Linux) runs `oats schedule tick --host` once a minute. The tick
23
+ evaluates only the current minute, launches what is due through the same
24
+ `spawn`, `session start` and `session input` paths you use by hand, polls the
25
+ triggers that are due, records what it observed, and exits. Minutes missed
26
+ while the machine slept are skipped, never replayed; there are no retries and
27
+ no queue. A schedule on a registered server keeps running while your laptop
28
+ sleeps.
29
+
30
+ The design is in the
31
+ [knowledge-operations design record](design/2026-09-26-okf-knowledge-operations.md#23-triggers)
32
+ (§2.3 and §2.3a).
20
33
 
21
34
  ## Files
22
35
 
23
- - `<workspace>/oats-schedules.json` — the definitions (`{version: 1|2, jobs:
24
- {<id>: ...}}`). A new file is version 1. A version-2 file (written by 0.24–0.25
25
- for captured definitions) is still read; it is never rewritten to version 1.
26
- Commit the file if you want the schedule shared with the team.
27
- - `<workspace>/.agents/schedules/state.json` — last attempted minute and
28
- last run per job (gitignored), plus one lock directory per running job.
29
- - `~/.oats/schedules/registry.json` — the host registry: which scopes the
30
- host ticks, `maxConcurrent` (default 1: running scheduled jobs), the tick
31
- interval, and `triggersMaxConcurrent` (absent by default: no host cap; a
32
- positive integer caps trigger-spawned live instances across the host's
33
- scopes). The two caps are separate: a running scheduled job never holds a
34
- trigger, and a trigger's instances never hold a schedule. One host
35
- lock serializes ticks, run-now, reconcile and remove; it is never reclaimed
36
- by another process: a lock whose owner is unreadable or gone is reported
37
- with the directory to remove, and the holder removes its own lock on exit
38
- and on SIGINT/SIGTERM. Definition edits take a short per-scope lock.
39
-
40
- Captured (versioned) definitions are refused (the captured/portable path was removed in 0.26):
41
- see [Captured definitions](#captured-definitions-removed-in-026).
36
+ | File | Holds |
37
+ | --- | --- |
38
+ | `<deployment>/oats-schedules.json` | This machine's local definitions, `{version: 1, jobs: {<id>: …}}`, local triggers included (`kind: "trigger"`). |
39
+ | `<deployment>/.agents/automations/snapshot.json` | The workspace definitions discovered from the members. |
40
+ | `<deployment>/.agents/schedules/` | Run state: `state.json` (last minute and recent runs per job), `triggers.json` (polls, pending events, fired keys) and one lock directory per running job. |
41
+ | `~/.oats/schedules/registry.json` | The deployments this host ticks, `maxConcurrent` (default 1: running scheduled jobs) and `triggersMaxConcurrent` (absent: no host cap on trigger-spawned live instances). The two caps are separate. |
42
+
43
+ One host lock serializes ticks, run-now, reconcile and remove. It is never
44
+ reclaimed by another process: a lock whose owner is gone is reported with the
45
+ directory to remove.
42
46
 
43
47
  ## Kinds
44
48
 
45
- - **spawn** `{id, enabled, cron, tz, kind: "spawn", agent, agentsRoot?,
46
- repo?, backend?, purpose?, task, launchConfig?, harness?, model?, yolo?, wake?}` — every
47
- due minute launches one disposable instance of `agent` with the same
48
- options `oats spawn` takes. `model` is a model id (a letter or digit, then
49
- letters, digits and `. _ : / @ + - [ ]`, at most 128 characters, or
50
- `@native-default`), and `agent` and `repo` never start with `-`: an
51
- automation's values reach the child `oats spawn` as single `--flag=value`
52
- tokens and can never be read as options of their own. `agentsRoot` names the exact agents root that
53
- holds the soul (it must lie inside the workspace and defaults to the
54
- workspace's own root); it is what tells same-named souls in different
55
- member repositories apart. `repo` is the work repository, as `--repo`.
56
- Each run is named `<agent>-<purpose or id>-<YYYYMMDDHHMM>`. Instance names
57
- are at most 64 characters, so a definition whose run names would be longer
58
- is refused when it is saved (`E_SCHEDULE_INVALID`, field `purpose` or `id`). The task gets a trailing schedule block naming the job and the
59
- minute and ending with `oats retire --self`. An optional `wake` object
60
- (`{cron, tz, message}`) attaches a wake schedule to each launched instance;
61
- nothing is attached unless you ask.
62
- - **command** `{id, enabled, cron, tz, kind: "command", cwd, argv}` — runs
63
- an oats-only argv (`argv[0]` is `oats`, no shell) in `cwd`, which must be
64
- inside the workspace. The runner parses the command's envelope and tracks
65
- any instance it names. A provider can return an independent worker launched
66
- from durable context; the job follows that worker until its home is gone.
67
- Command return is not task completion. Avoid binding durable work to a
68
- disposable source-home cwd; see [OKF v2 source jobs](#okf-v2-source-jobs).
69
- An argv carrying a captured selector (`--deployment`, `--resolution`,
70
- `--artifact-set`) is refused when saved (`E_SCHEDULE_INVALID`, field `argv`).
71
- - **wake** `{id, enabled, cron, tz, kind: "wake", home, message}` — every
72
- due minute inspects the instance at `home` through its session receipts.
73
- Running: `message` is delivered once with `session input`. Not running
74
- (absent, dead pane or fallback shell): the same home is started with
49
+ Every definition carries `id`, `enabled`, `cron`, `tz` and `kind`. `cron` has
50
+ five fields (minute hour day month weekday) and `tz` is a required IANA zone;
51
+ both are evaluated by the croner library.
52
+
53
+ - **spawn** `{…, agent, agentsRoot?, repo?, backend?, purpose?, task,
54
+ launchConfig?, harness?, model?, yolo?, wake?}` — every due minute launches
55
+ one disposable instance of `agent` with the options `oats spawn` takes.
56
+ `agentsRoot`, when given, must be the deployment's `agents/` root; `repo`
57
+ is the work repository, as `--repo`. `model` is a model id (a letter or
58
+ digit, then letters, digits and `. _ : / @ + - [ ]`, at most 128
59
+ characters) or `@native-default`; `agent` and `repo` never start with `-`,
60
+ so no value can be read as an option of the child `oats spawn`. Each run is
61
+ named `<agent>-<purpose or id>-<YYYYMMDDHHMM>`, at most 64 characters (a
62
+ longer one is refused when saved, `E_SCHEDULE_INVALID`). The task gets a
63
+ trailing block naming the job and the minute and ending with `oats retire
64
+ --self`. `wake` (`{cron, tz, message}`) attaches a wake schedule to each
65
+ launched instance.
66
+ - **command** `{…, cwd, argv}` — runs an oats-only argv (`argv[0]` is `oats`,
67
+ no shell; `oats schedule` itself is refused) in `cwd`, an existing directory
68
+ inside the deployment. The runner tracks any instance the command's
69
+ envelope names, including an independent worker it reports, until its home
70
+ is gone. A command's return is not task completion.
71
+ - **wake** `{…, home, message}` — every due minute inspects the instance at
72
+ `home`. Running: `message` is delivered once as terminal input (bracketed
73
+ paste plus Enter), never an interrupt. Not running: the home is started with
75
74
  `session start` and the message becomes the job's one pending delivery,
76
- completed on a later tick, any tick, as soon as the session is active; the
77
- home is started again only at due minutes, never every minute, so a
78
- harness that keeps exiting is not restarted in a loop. A job holds at most
79
- one pending delivery: a due minute while one is pending adds nothing.
80
- - **operation** `{id, enabled, cron, tz, kind: "operation", operation, home}`
81
- — runs a provider operation such as `knowledge:harvest` in the instance at
82
- `home` through `oats operation run <layer>:<name> --home <home>`. The
83
- provider is whatever fills that layer for the home when the job runs (its
84
- snapshot), not something stored in the job, so the job stays valid across
85
- provider changes and a GUI can list and edit it without parsing argv.
86
- Admission, tracking and reconciliation are those of a command job: a
87
- launch receipt the provider answers (a harvester it spawned) is followed
88
- until that home is gone; the source home is never treated as a launch.
89
- Unobservable or still starting: skipped with the reason, delivery kept
90
- pending. Whether a running harness is busy cannot be seen from the
91
- terminal: delivery is terminal input (bracketed paste plus Enter), never an
92
- interrupt, never Ctrl-C, never into a stopped or starting shell. Word wake
93
- messages so that receiving one again is harmless.
94
-
95
- `cron` has five fields (minute hour day month weekday) and `tz` is a
96
- required IANA zone; both are evaluated by the croner library. `--wake-every
97
- N` at spawn time means `*/N * * * *`: every 7 fires at :00, :07, ... :56 and
98
- then :00 again, so 1, 5, 10, 15 and 30 give an even cadence.
75
+ completed on a later tick once the session is active; the home is started
76
+ again only at due minutes, so a harness that keeps exiting is not restarted
77
+ in a loop. Unobservable or still starting: skipped, delivery kept pending.
78
+ Word wake messages so that receiving one again is harmless.
79
+ - **operation** `{…, operation, home}` — runs a provider operation such as
80
+ `knowledge:harvest` in the instance at `home` through `oats operation run
81
+ <layer>:<name> --home <home>`. The provider is whatever fills that layer
82
+ when the job runs. Tracking is that of a command job.
83
+
84
+ `--wake-every N` at spawn time means `*/N * * * *`: every 7 fires at :00, :07,
85
+ … :56 and then :00 again, so 1, 5, 10, 15 and 30 give an even cadence.
99
86
 
100
87
  ## Triggers
101
88
 
102
- A **trigger** (OATS 0.28.0, feature `triggers`) is an event-driven schedule:
103
- "when EVENT matches, spawn a NEW instance of SOUL with TASK, in TEAMS". It is
104
- stored in the same `oats-schedules.json` as a job of `kind: "trigger"`,
105
- managed with `oats trigger …` (never `oats schedule …`, which neither lists nor
106
- edits one), and evaluated by the same host tick (`oats schedule tick --host`,
107
- and `oats schedule tick` for one scope). There is no daemon and no webhook: it
108
- runs only on the host that holds the scope, with **that host's own
109
- credentials**; a definition carries none.
110
-
111
- **Credentials reach the tick through the host timer, not your shell.** The
112
- timer (a user LaunchAgent on macOS, a `systemd --user` unit on Linux) runs the
113
- tick with its own environment, which sets only `PATH` and `OATS_HOME_DIR`. `gh`
114
- logged in with the keyring or its config file under your HOME works there. A
115
- `GH_TOKEN` or `GITHUB_TOKEN` exported in your shell does not reach it.
116
- `oats trigger test` reports where `gh`'s credential comes from (`gh.credentialSource`:
117
- `keyring`, `config`, `env:<VAR>`) and warns when the timer cannot reach it.
89
+ A **trigger** is an event-driven spawn: "when EVENT matches, spawn a NEW
90
+ instance of SOUL with TASK". It is managed with `oats trigger …` (`oats
91
+ schedule …` neither lists nor edits one) and evaluated by the same host tick.
92
+ There is no webhook. It runs only on the host that holds it, with **that
93
+ host's own credentials**; a definition carries none.
94
+
95
+ The host timer runs the tick with its own environment (only `PATH` and
96
+ `OATS_HOME_DIR`): `gh` logged in with the keyring or its config file works
97
+ there, but a `GH_TOKEN` exported in your shell does not. `oats trigger test`
98
+ reports `gh.credentialSource` (`keyring`, `config`, `env:<VAR>`) and warns
99
+ when the timer cannot reach it.
118
100
 
119
101
  ```json
120
102
  { "id": "okf-harvest-review", "enabled": true, "kind": "trigger",
@@ -123,100 +105,74 @@ logged in with the keyring or its config file under your HOME works there. A
123
105
  "labels": ["okf-harvest"], "base": "main", "poll": "2m" },
124
106
  "spawn": { "soul": "oats.okf/knowledge-maintainer", "purpose": "review-pr-{number}",
125
107
  "task": "Review knowledge-base PR {repo}#{number}. Load knowledge-review first.",
126
- "teams": ["okf"], "harness": "claude", "model": "opus" },
108
+ "harness": "claude", "model": "opus" },
127
109
  "concurrency": { "max": 2, "perKey": 1 } }
128
110
  ```
129
111
 
130
- - **Source** `github.pull_request` (the only one in v1): the tick polls the
131
- repository's open pull requests with the host's `gh` (`gh api repos/<owner>/<repo>/pulls`,
132
- `state=open`, most recently updated first) every `poll` (default `2m`, at
133
- least `1m`). `labels` (all must be present) and `base` filter them. A repo is
134
- `github.com/<owner>/<repo>`; another host is passed to `gh` as `--hostname`.
112
+ - **Source.** `github.pull_request` is the only source. The tick polls the
113
+ repository's open pull requests with the host's `gh` (`gh api
114
+ repos/<owner>/<repo>/pulls`, `state=open`, most recently updated first)
115
+ every `poll` (default `2m`, at least `1m`). `labels` (all must be present)
116
+ and `base` filter them. A repo is `github.com/<owner>/<repo>`; another host
117
+ is passed to `gh` as `--hostname`.
135
118
  - **Events** are inferred poll over poll: `opened` (a PR first seen, not a
136
119
  draft; the first poll sees every open PR), `reopened` (seen closed, open
137
120
  again), `ready_for_review` (was a draft), `labeled` (now carries the filter
138
- labels it lacked; without a filter, any new label), `synchronize` (a new
121
+ labels it lacked; without a filter, any new label) and `synchronize` (a new
139
122
  head commit).
140
- - **Dedup and retry.** Each event has a key
123
+ - **Dedup, at least once.** Each event has a key
141
124
  `<trigger>:<repo>#<number>:<event>:<stamp>` (`created_at` for `opened`, the
142
- head SHA for `synchronize`, `updated_at` otherwise). A key is recorded as
143
- fired **only after a successful spawn**; until then the event stays pending
144
- and is retried at every poll, and dropped when its PR closes.
145
- - **At least once, not exactly once.** The fired key is written after the
146
- spawn returns. If the tick dies in between (a crash, a kill, the host going
147
- down), the spawned instance exists but the key does not, and the next poll
148
- spawns the event again. Concurrency still applies to that retry: with the
149
- default `perKey: 1` the first instance is live, so the event is `held` rather
150
- than spawned twice, and it fires once that instance retires. A trigger's soul
151
- should therefore tolerate a second run on the same PR event (a review that
152
- finds its own earlier review, for example).
153
- - **Concurrency.** `max` (default 1) bounds the live instances of the trigger,
154
- `perKey` (default 1) those of one PR; both are counted from the homes'
155
- `instance.json.trigger` records, so a retired instance frees its slot. The
156
- host registry's `triggersMaxConcurrent`, when set, bounds every trigger's
157
- live instances together. An event over a bound stays pending (`held`). A
158
- newer push supersedes a pending `synchronize` for an older head of the same
159
- PR, so only the newest head is reviewed.
160
- - **The owner acts on the repository's host.** A workspace trigger's `owner`
161
- and `on.repo` must be on the same GitHub host (`E_TRIGGER_INVALID`, field
162
- `owner`): the owner check and the poll ask gh on that one host.
163
- - **The spawn** is `oats spawn` (the same path as a scheduled spawn). `soul` is
164
- bare or qualified (`<package>/<soul>`). `purpose` (default
165
- `{trigger}-{number}`, must render to a slug) and `task` are templated from
166
- **only** `{repo} {number} {url} {event} {headSha} {trigger}`: a pull
167
- request's title and body are untrusted and never reach the task (a template
168
- naming any other field is refused). `teams` becomes the messaging
169
- capability's `join=` setting (as `--provider <messaging cap> join=<labels>`).
170
- `launchConfig` (a launch configuration in the running host's
171
- `oats-local.yaml` `launch-configs`), `harness`, `model`, `yolo`, `backend`
172
- are as for schedules; a package template may expose any of them as a
173
- parameter (`"path": "spawn.launchConfig"`).
125
+ head SHA for `synchronize`, `updated_at` otherwise), recorded as fired
126
+ **only after a successful spawn**. Until then the event stays pending, is
127
+ retried at every poll, and is dropped when its PR closes. A tick that dies
128
+ between the spawn and the record spawns the event again on the next poll
129
+ (held by `perKey` while the first instance lives), so a trigger's soul
130
+ should tolerate a second run on the same event.
131
+ - **Concurrency.** `max` (default 1) bounds the trigger's live instances and
132
+ `perKey` (default 1) those of one PR, counted from the homes'
133
+ `instance.json.trigger` records; a retired instance frees its slot. An event
134
+ over a bound stays pending (`held`). A newer push supersedes a pending
135
+ `synchronize` for an older head of the same PR.
136
+ - **The spawn** is `oats spawn`. `soul` is bare or qualified
137
+ (`<package>/<soul>`). `purpose` (default `{trigger}-{number}`) and `task`
138
+ are templated from **only** `{repo} {number} {url} {event} {headSha}
139
+ {trigger}`: a pull request's title and body are untrusted and never reach
140
+ the task. `teams` (optional) becomes the messaging capability's `join=`
141
+ setting (`E_TRIGGER_TEAMS` when the soul has no messaging capability).
142
+ `launchConfig`, `harness`, `model`, `yolo` and `backend` are as for
143
+ schedules.
174
144
  - **The event reaches the instance** as `OATS_TRIGGER_EVENT_FILE`
175
145
  (`<home>/.oats/trigger-event.json`: `{ trigger, source, repo, number, url,
176
146
  event, headSha, labels, observedAt, key }`), given to the spawn hooks and the
177
- harness; `instance.json.trigger` records `{ id, key, source, repo, number,
178
- url, event, headSha, observedAt, eventFile }`. The task ends with a short
179
- block naming the event file.
180
- - **State** lives in `<scope>/.agents/schedules/triggers.json` (last poll, the
181
- PRs seen, pending events, fired keys, the last error).
147
+ harness, and is recorded in `instance.json.trigger`.
182
148
 
183
149
  ```sh
184
150
  oats trigger add --file trigger.json # or:
185
151
  oats trigger add --from oats.okf:harvest-review --set repo=github.com/acme/knowledge [--id <id>]
186
152
  oats trigger list | show <id> | enable <id> | disable <id> | remove <id>
187
- oats trigger test <id> # dry run: gh auth + credential source, repo + permissions (push/maintain/admin), the soul resolves,
188
- # its messaging capability, the teams declared, what WOULD fire now; spawns nothing
189
- oats trigger status [<id>] # last poll, next due, pending, fired keys (time, instance), live vs max, last error
153
+ oats trigger test <id> # dry run: gh credentials, repo permissions, the soul, what WOULD fire
154
+ oats trigger status [<id>] # last poll, next due, pending and fired events, live vs max, last error
190
155
  ```
191
156
 
192
157
  All take `--dir` and `--json` (`triggerApi: 1`). `remove` leaves the instances
193
- it spawned running. `oats schedule list` does not list triggers, but it counts
158
+ it spawned running. `oats schedule list` does not list triggers but counts
194
159
  them (`triggers: { count, command: "oats trigger list" }`, and a line in text
195
160
  mode). Errors: `E_TRIGGER_INVALID { field }`, `E_TRIGGER_EXISTS`,
196
- `E_TRIGGER_UNKNOWN`, `E_BAD_ARGS`.
161
+ `E_TRIGGER_UNKNOWN`, `E_TRIGGER_TEAMS`, `E_BAD_ARGS`.
197
162
 
198
163
  **Package trigger templates.** A package may declare `triggers: [{ id, file }]`
199
- in `oats-package.json`. Each file is `{ parameters: { <name>: { path,
200
- required?, default?, description? } }, definition: { …a trigger… } }`.
201
- `oats trigger add --from <package>:<id>` reads it at the locked commit;
202
- `--set <name>=<value>` fills a parameter at its dotted `path` (a list value is
203
- comma-separated); a required parameter without a value is `E_BAD_ARGS
204
- { missing }` naming it. The trigger records `template: { package, version,
205
- commit, template }`.
164
+ in `oats-package.json`, each file `{ parameters: { <name>: { path, required?,
165
+ default?, description? } }, definition }`. `oats trigger add --from
166
+ <package>:<id>` reads it at the locked commit, and `--set <name>=<value>`
167
+ fills a parameter at its dotted `path` (a list value is comma-separated; a
168
+ missing required one is `E_BAD_ARGS { missing }`). See
169
+ [packages.md](packages.md#trigger-templates).
206
170
 
207
171
  ## Workspace triggers and schedules
208
172
 
209
- (OATS 0.29.0, feature `automations`.) A trigger or a schedule is defined at one of
210
- two levels:
211
-
212
- - **in the workspace**: a file committed in a confirmed member repository, shared
213
- through Git and named `<member>/<id>`. This is the default for anything a team
214
- relies on.
215
- - **locally**: in the deployment's `oats-schedules.json` (`oats trigger add`,
216
- `oats schedule add`), machine-private and named `local/<id>`.
217
-
218
- The two kinds stay separate at every step. Each has its own folder, its own file
219
- kind, its own ids, its own commands, its own list and its own opt-out.
173
+ Anything a team relies on belongs in a confirmed member repository, shared
174
+ through Git and addressed `<member>/<id>`. The two kinds stay separate: each
175
+ has its own folder, file kind, ids, commands, list and opt-out.
220
176
 
221
177
  | | trigger | schedule |
222
178
  | --- | --- | --- |
@@ -224,11 +180,10 @@ kind, its own ids, its own commands, its own list and its own opt-out.
224
180
  | file name anywhere in the member | `*.oats-trigger.yaml` | `*.oats-schedule.yaml` |
225
181
  | `kind:` | `oats-trigger` | `oats-schedule` |
226
182
  | body | `from:` + `set:` (a package template), or `on`, `spawn`, `concurrency` as above | `run: spawn \| command`, `cron`, `tz`, `agent`, `task`, `purpose`, `launchConfig`, `harness`, `model`, `yolo`, `backend`, `wake`, `argv`, `cwd` |
227
- | commands | `oats trigger …` | `oats schedule …` |
228
183
  | opt-out on this host | `triggers.disabled` | `schedules.disabled` |
229
184
 
230
- Every `.yaml`/`.yml` under a canonical folder is a candidate, and so is a file with
231
- the kind's suffix anywhere in the member (`.yml` works too), e.g.
185
+ Every `.yaml`/`.yml` under a canonical folder is a candidate, and so is a file
186
+ with the kind's suffix anywhere in the member (`.yml` works too), for example
232
187
  `services/billing/nightly.oats-schedule.yaml` beside the code it concerns.
233
188
  `oats-package/`, `.git/` and `node_modules/` are never scanned.
234
189
 
@@ -256,216 +211,178 @@ runsOn: ana-laptop
256
211
  owner: github.com/ana
257
212
  ```
258
213
 
259
- - **The file describes itself.** It carries `kind` and `schemaVersion: 1`. A
260
- candidate of the wrong kind (a schedule in `oats-triggers/`) or without one is an
261
- `E_AUTOMATION_SCHEMA` problem, never silently skipped.
262
- - **The id** is `id:`, else the filename stem. The same id twice in one member for
263
- one kind is `E_AUTOMATION_DUPLICATE`, naming both paths; the second file is not
264
- listed. A trigger and a schedule may share an id: they are different things.
265
- - **A workspace schedule is `run: spawn` or `run: command`.** A `command`'s `cwd` is
266
- relative to the deployment. `wake` and `operation` target an instance home on one
267
- machine, so they stay local.
268
-
269
- **Who runs it.** A host runs a workspace trigger or schedule only when both of these
270
- hold:
214
+ - **The header.** Every file carries `kind` and `schemaVersion: 1`, plus
215
+ `runsOn` and `owner`, and optionally `id`, `description` and `enabled`. A
216
+ candidate of the wrong kind (a schedule in `oats-triggers/`) or without one
217
+ is an `E_AUTOMATION_SCHEMA` problem, never silently skipped.
218
+ - **The id** is `id:`, else the filename stem. The same id twice in one member
219
+ for one kind is `E_AUTOMATION_DUPLICATE`, naming both paths; the second file
220
+ is not listed. A trigger and a schedule may share an id. A member named
221
+ `local` is refused, because `local/<id>` names this host's own definitions.
222
+ - **A workspace schedule is `run: spawn` or `run: command`.** A command's
223
+ `cwd` is relative to the deployment and must stay inside it. `wake` and
224
+ `operation` target an instance home on one machine, so they stay local.
225
+ - **A workspace trigger's `owner` and `on.repo` must be on the same GitHub
226
+ host** (`E_TRIGGER_INVALID`, field `owner`).
227
+
228
+ **Who runs it.** A host runs a workspace trigger or schedule only when all
229
+ three hold:
271
230
 
272
231
  1. its `runsOn` is this host's `host.name` in `oats-local.yaml`;
273
- 2. the host's authenticated `gh` account (`gh api user`, asked once per tick) is its
274
- `owner`.
275
-
276
- Otherwise the item is listed with a reason:
277
-
278
- - `assigned-elsewhere`: another host runs it;
279
- - `owner-mismatch`: this host is named, but its `gh` is logged in as someone else or
280
- not at all. Nothing runs, the tick reports it, and `oats trigger test` says so;
281
- - `host-unnamed`: this host has no `host.name`.
282
-
283
- So exactly one machine runs it, and consent is explicit: the machine's operator
284
- named the host and is logged in as the account.
285
-
286
- **Opting out** without a commit: `oats trigger disable <member>/<id>` writes
232
+ 2. the host's authenticated `gh` account (`gh api user`, asked once per tick)
233
+ is its `owner`;
234
+ 3. this host's `oats-local.yaml` trusts it (0.30):
235
+
236
+ ```yaml
237
+ automations:
238
+ trust:
239
+ - agents/pr-review # <member>/<id>, as the list names it
240
+ # or trust: "*" # every automation the workspace places on this host
241
+ ```
242
+
243
+ Otherwise the item is listed with a reason: `assigned-elsewhere`,
244
+ `owner-mismatch` (this host is named, but its `gh` is logged in as someone
245
+ else or not at all), `host-unnamed`, or `untrusted` (placed here but not
246
+ trusted: it never runs, and `oats workspace status` warns with the exact
247
+ line to add). Both names come from a commit, so anyone who can commit to a
248
+ member could name your host; trust is the operator's own yes. A trust entry
249
+ that names no workspace automation is a warning (`automation-trust-stale`),
250
+ not an error: its member may not have synced yet. Your own `oats trigger
251
+ add` / `oats schedule add` definitions need no trust.
252
+
253
+ **Opting out on one host.** `oats trigger disable <member>/<id>` writes
287
254
  `triggers.disabled`, and `oats schedule disable <member>/<id>` writes
288
- `schedules.disabled`, in `oats-local.yaml`. `enable` removes the entry. A
289
- workspace definition is never edited or removed here (`update` and `remove` are
290
- `E_AUTOMATION_WORKSPACE`): change the file in Git.
255
+ `schedules.disabled`, in `oats-local.yaml`; `enable` removes the entry. A
256
+ workspace definition is never edited or removed from the CLI (`update` and
257
+ `remove` answer `E_AUTOMATION_WORKSPACE`): change the file in Git.
291
258
 
292
259
  **Refresh.**
293
260
 
294
- - `oats sync` discovers the workspace triggers and schedules of the confirmed
295
- members into a snapshot, `.agents/automations/snapshot.json` in the deployment,
296
- with one list per kind. It takes one tree listing per member commit.
297
- - The host tick reads that snapshot. It refreshes it (`oats automations refresh`)
298
- when the snapshot is more than ten minutes old, at most once per interval. When
299
- the refresh fails, the last good snapshot keeps serving.
300
- - A change in Git therefore reaches the named host within about ten minutes.
301
- - The run state (dedup keys, last poll, last run) stays per host and local. A
302
- workspace schedule's job lock and state are keyed `<member>~<id>`.
303
- - A trigger template (`from:`) is instantiated when the snapshot is taken, at the
304
- commit the host's lock pins.
305
-
306
- **Writing one.** Add `--workspace <member> --runs-on <host> --owner <host>/<login>`
307
- to `oats trigger add` or `oats schedule add`:
308
-
309
- - Run inside a checkout of that member, it writes `oats-triggers/<id>.yaml` or
310
- `oats-schedules/<id>.yaml` there, for you to commit and push.
311
- - Anywhere else, it prints the file.
312
- - Either way, the file is read back and validated first.
313
- - `oats trigger test <member>/<id>` checks the placement, and everything else it
314
- checked before, on this host.
315
-
316
- Everything above still holds: the soul must resolve here, templates name only the
317
- whitelisted fields, a PR's text is never interpolated, and no definition carries a
318
- credential.
319
-
320
- ## Captured definitions (removed in 0.26)
321
-
322
- 0.24–0.25 could save captured command definitions: `definitionVersion`,
323
- `recurrencePolicy` (`capture` or `prepare-on-tick`), an `execution` template and a
324
- `preparation` request, run against a captured deployment/resolution. That path
325
- was removed in 0.26:
326
-
327
- - `add` and `update` refuse any of those four keys (`E_SCHEDULE_INVALID`, the
328
- key as `field`), locally and, with `--server`, before anything is forwarded.
329
- - A stored captured definition is invalid on its own job: every tick reports it
330
- (`invalid`, "captured schedules are refused (the captured/portable path was removed in 0.26) …") and never runs it;
331
- the rest of the scope's jobs continue. `list`/`show` report its
332
- `executionStatus` as `{kind:"invalid", …, reason}`.
333
- - A captured attempt or job lock left mid-run by 0.25 is reported on that job
334
- (`executionStatus.intent`, `reconcile` refuses with `E_SCHEDULE_INVALID`) and is
335
- never run, adopted or released. A held captured lock keeps its launch slot
336
- until the job is gone: remove it with `oats schedule remove --force <id>`, or
337
- re-add it without the captured keys.
338
-
339
- Every other definition is a plain one; `list`/`show` report its `executionStatus`
340
- as `{kind:"legacy",capture:"unknown",migrationRequired:true}` (a released wire,
341
- kept as it was).
261
+ - `oats sync` (or `oats automations refresh`) discovers the confirmed
262
+ members' definitions into `.agents/automations/snapshot.json`. A trigger
263
+ template (`from:`) is instantiated then, at the commit the lock pins.
264
+ - The host tick refreshes the snapshot when it is more than ten minutes old;
265
+ when a refresh fails, the last good snapshot keeps serving. A change in Git
266
+ reaches the named host within about ten minutes.
267
+ - Run state stays local to each host; a workspace schedule's lock and state
268
+ are keyed `<member>~<id>`.
269
+
270
+ **Writing one.** `oats trigger add` or `oats schedule add` with `--workspace
271
+ <member> --runs-on <host> --owner <host>/<login>` validates the file, then
272
+ writes `oats-triggers/<id>.yaml` or `oats-schedules/<id>.yaml` inside a
273
+ checkout of that member (for you to commit and push), or prints it anywhere
274
+ else. `oats trigger test <member>/<id>` and `oats schedule test <member>/<id>`
275
+ check the placement and everything else on this host.
342
276
 
343
277
  ## Commands
344
278
 
345
279
  ```sh
346
- oats schedule add <id> --file spec.json --dir <workspace> --json
280
+ oats schedule add <id> --file spec.json [--dir <deployment>] [--json]
347
281
  oats schedule update <id> --file spec.json
348
- oats schedule list | show <id> | enable <id> | disable <id> | remove <id>
349
- oats schedule run <id> # now, under the same lock and bound
350
- oats schedule test <id> # dry run: where it runs, whether its soul resolves, when it is next due; spawns nothing
351
- oats schedule tick --dry-run # what would run this minute, launching nothing
282
+ oats schedule list | show <id> | enable <id> | disable <id> | remove <id> [--force]
283
+ oats schedule run <id> [--force] # now, under the same lock and bound
284
+ oats schedule test <id> # dry run: where it runs, whether its soul resolves, when it is next due
285
+ oats schedule tick [--dry-run] # evaluate this deployment now; --dry-run launches nothing
352
286
  oats schedule reconcile <id> [--clear] # resolve an attempt whose result was never recorded
353
- oats schedule host install # register this scope and install the ONE host timer (idempotent while active)
287
+ oats schedule host install # register this deployment and install the one host timer (idempotent)
354
288
  oats schedule host status | uninstall
355
289
  oats spawn <agent> ... --wake-every 15 --wake-message "Anything new?" # or --wake-file spec.json
356
290
  ```
357
291
 
358
- Every subcommand takes `--server <id>` instead of `--dir`: it then runs on
359
- that host, in its registered workspace, because schedules are host-owned.
292
+ `<id>` is `local/<id>` (or the bare id) or `<member>/<id>`. `host uninstall`
293
+ unregisters the deployment and removes the timer once none is registered.
294
+ Every `oats schedule` subcommand takes `--server <id>` instead of `--dir` to
295
+ run on that registered server.
296
+
297
+ `oats schedule list --json` answers:
298
+
299
+ ```text
300
+ { scope, scheduleApi: 2, scheduleHistoryApi: 3,
301
+ integrity: { sources: [{ path, status, bytes }] },
302
+ host: { name, ghUser: { <gh host>: <login> | null } },
303
+ schedules: [ <row> ],
304
+ triggers: { count, command: "oats trigger list" },
305
+ snapshot: { takenAt, problems } | null,
306
+ scheduler: { installed, active, unit?, lastTick, maxConcurrent, tickIntervalSec,
307
+ workspace, registered, workspaces, live } }
308
+ ```
360
309
 
361
- `list --json` answers `{schedules: [{id, ...definition, nextDue, lastRun,
362
- running}], scheduler: {installed, active, lastTick, maxConcurrent, ...}}`
363
- (`oats trigger list --json` carries the same `scheduler`).
364
- `active` is what the OS reports about the timer, not whether a file exists.
310
+ Each row is the stored definition plus `id` (bare for a local schedule,
311
+ `<member>/<id>` for a workspace one), `qualifiedId`, `origin`, `owner`,
312
+ `runsOn`, `runsHere`, `reason`, `enabledHere`, `soul`, `nextDue`, `lastRun`,
313
+ `recentRuns` and `running`; an unreadable row carries `unreadable: { code,
314
+ message }` instead of failing the list. `scheduler.active` is what the OS
315
+ reports about the timer. `oats trigger list --json` carries the same
316
+ `scheduler`. The field-level contract is in
317
+ [desktop-cli-api.md](desktop-cli-api.md).
365
318
 
366
319
  ## What a run reports
367
320
 
368
- `launched` (spawn or command returned), `active` (the instance is running;
369
- a home whose retirement is pending still counts, its harness may be alive),
370
- `ended` (its home is gone), `stopped` (home present, nothing running: needs
371
- attention, never removed for you), `launch-failed`, `unknown`, and for wake
372
- jobs `delivered`, `started` or `skipped`. The kernel never claims a task
373
- succeeded.
374
-
375
- A run recorded by 0.24–0.25 may also read `blocked` (a captured admission that
376
- refused before a launch slot); 0.26 never produces it.
377
-
378
- `unknown` means the launch's side effects are unconfirmed: a command timed
379
- out or answered no envelope, an envelope named an instance the roster
380
- cannot place, or an attempt was never recorded. The job keeps its slot and
381
- is skipped until `oats schedule reconcile <id>`. Reconcile adopts only an
382
- attributable receipt: a spawn job's instance is named deterministically for its
383
- minute; a command job's, only the instance its answer named. Nothing is inferred
384
- from file times. Observation validates custody before releasing
385
- slots, and unresolved attempts remain held even in the crash gap before a lock
386
- exists. Ordinary removal refuses those attempts; force-forget remains explicit. A command whose answer named nothing stays
387
- unknown; check the roster and the host by hand, then
388
- `oats schedule reconcile <id> --clear` records launch-failed and frees the
389
- slot (or `remove --force` forgets the job).
390
-
391
- A wake job that starts a stopped home holds a launch slot while that
392
- harness is starting, active, retiring or unobservable, and releases it when
393
- the harness is proven stopped (the session start receipt's exit marker for
394
- that launch, or a home that no longer has a session) or the home is gone.
395
- A persistent home that outlives its process does not keep a slot. Delivering
396
- a message to a home that is already running takes no slot. The host tick
397
- observes every registered scope first, then admits due jobs in one
398
- host-wide order, least recently launched first (only an actual harness
399
- launch counts; a skipped or pending job keeps its place at the front), so
400
- one frequent job in one scope cannot keep the only slot forever. An invalid
401
- or malformed definition is reported on that job and the rest of the tick
321
+ `launched` (spawn or command returned), `active` (the instance is running; a
322
+ home whose retirement is pending still counts), `ended` (its home is gone),
323
+ `stopped` (home present, nothing running: needs attention, never removed for
324
+ you), `launch-failed`, `unknown`, and for wake jobs `delivered`, `started` or
325
+ `skipped`. The kernel never claims a task succeeded.
326
+
327
+ `unknown` means the launch's side effects are unconfirmed: a command timed out
328
+ or answered no envelope, or an attempt was never recorded. The job keeps its
329
+ slot and is skipped until `oats schedule reconcile <id>`, which adopts only an
330
+ attributable receipt (a spawn job's instance, named for its minute, or the
331
+ instance a command's answer named). When nothing is attributable, check the
332
+ roster and the host by hand, then `reconcile <id> --clear` records
333
+ `launch-failed` and frees the slot.
334
+
335
+ **Slots.** A wake job that starts a stopped home holds a launch slot until the
336
+ harness is proven stopped or the home is gone; delivering to a running home
337
+ takes none. The host tick admits due jobs in one host-wide order, least
338
+ recently launched first, so one frequent job cannot keep the only slot
339
+ forever. An invalid definition is reported on its job; the rest of the tick
402
340
  continues.
403
341
 
404
- `disable` never stops anything. `update` never touches a running instance,
405
- and while a job holds a slot or has an unresolved attempt its complete
406
- execution identity, kind and target cannot
407
- change; cron, tz and enabled can. A cold wake persists its slot before
408
- the session start runs and keeps it on any start exception, whatever its code
409
- (the kernel can refuse while recording, after the session exists); the next
410
- observation releases it once the harness is proven stopped or absent, one tick
411
- at worst.
412
- `remove` refuses while the job's instance is still tracked (`--force`
413
- forgets the job without stopping anything). Retiring an instance removes the
414
- wake jobs bound to its home; a wake whose home is gone otherwise stays
415
- listed with its skipped reason.
342
+ **Changing a job.** `disable` never stops anything. `update` never touches a
343
+ running instance, and while a job holds a slot or has an unresolved attempt
344
+ only `cron`, `tz` and `enabled` can change. `remove` refuses while the job's
345
+ instance is tracked or its effects are unresolved (`--force` forgets the job
346
+ without stopping anything). Retiring an instance removes the wake jobs bound
347
+ to its home.
416
348
 
417
349
  ## Wake at spawn
418
350
 
419
- `oats spawn ... --wake-file <private JSON {cron, tz, message, enabled}>`
420
- saves a wake job `wake-<instance>` bound to the new home after the spawn
421
- succeeded. If the spawn succeeds but the save fails, the spawn result still
422
- carries the full instance receipt, plus `wakeScheduleError` and a warning;
423
- the instance is neither hidden nor spawned again.
424
-
425
- ## OKF v2 source jobs
426
-
427
- > **oats.okf 4.0.0:** a source and its job exist only where harvest is
428
- > effectively on (`harvest: on|off`, default off), and the job spawns the
429
- > harvester package soul. The review of its PR runs as a **trigger**, run by
430
- > the same host tick: a workspace file (`oats-triggers/okf-harvest-review.yaml`
431
- > in a member repo, `kind: oats-trigger`, with `runsOn` and `owner`), or a
432
- > local one in this file. See
433
- > [knowledge.md](knowledge.md#knowledge-operations), [Triggers](#triggers) and
434
- > [Workspace triggers and schedules](#workspace-triggers-and-schedules); the
435
- > trigger commands are `oats trigger …`.
436
-
437
- The [prepared OKF v2 runtime](knowledge.md) registers **one command job per
438
- source**, not a fleet sweep or a home-bound operation job. It runs from stable
439
- deployment context with argv equivalent to:
440
-
441
- ```text
442
- oats okf run-source --source /absolute/state/sources/UUID/source.json --soul domain-expert --json
443
- ```
444
-
445
- The descriptor and captured evidence live outside the disposable source.
446
- Registration (including explicit harvest after source migration) idempotently
447
- creates/verifies the definition; setup failures are reported for retry. A
448
- pre-existing disabled job is not silently re-enabled. Command execution clears
449
- invoking-instance identity and still passes normal capability activation/trust
450
- gates after source retirement.
451
-
452
- No timer is installed by registering a source or its job. An operator can
453
- inspect or explicitly install one from deployment context:
454
-
455
- ```bash
456
- oats okf inspect --source /absolute/state/sources/UUID/source.json --soul domain-expert --json
457
- oats okf setup --source /absolute/state/sources/UUID/source.json --soul domain-expert --json
458
- # Explicit host change; never part of a scaffold-only test:
459
- oats okf setup --source /absolute/state/sources/UUID/source.json --install-host --soul domain-expert --json
460
- # Definition-only disable; does not stop a worker or reconcile an executing job:
461
- oats okf setup --source /absolute/state/sources/UUID/source.json --disable --soul domain-expert --json
462
- ```
463
-
464
- Retirement captures/enqueues final evidence and does not synchronously remove
465
- its job under the scheduler's host lock or wait for a model/GitHub. A drained
466
- retired source returns empty; disable its job explicitly when appropriate.
467
- Source no-launch guards prevent automatic model starts, and final capture of
468
- a no-launch source disables its automatic processing. `inspect` distinguishes
469
- job definition from actual timer activity; an absent or inactive timer is not
470
- reported as enabled automation. The scheduler's launch/liveness receipts do not
471
- replace OKF's processing, delivery and merge-visible acceptance receipts.
351
+ `oats spawn ... --wake-file <JSON {cron, tz, message, enabled}>` (or
352
+ `--wake-every N --wake-message <text>`) saves a local wake job
353
+ `wake-<instance>` bound to the new home once the spawn succeeds. If the save
354
+ fails, the spawn result still carries the instance receipt, plus
355
+ `wakeScheduleError` and a warning.
356
+
357
+ ## Knowledge harvest jobs
358
+
359
+ oats.okf uses both mechanisms. The flow, the harvest switch and the package
360
+ souls are described in [knowledge.md](knowledge.md#knowledge-operations).
361
+
362
+ - **Harvest.** A source exists only where harvest is on (`oats-local.yaml`
363
+ `settings.oats.okf.harvest: on`; default off). oats.okf then registers one
364
+ local **command job per source**, `okf-<source id>`, which runs from the
365
+ deployment with argv equivalent to:
366
+
367
+ ```text
368
+ oats okf run-source --source /absolute/state/sources/UUID/source.json --soul domain-expert --json
369
+ ```
370
+
371
+ The job spawns the package soul `oats.okf/knowledge-harvester`. The source's
372
+ evidence lives outside its home, so the job keeps working after the source
373
+ instance retires; once a retired source is drained, oats.okf removes the
374
+ job. Registration never re-enables a disabled job.
375
+ `oats schedule disable okf-<source id>` is the emergency brake for one
376
+ source.
377
+ - **Review.** Each harvest PR is reviewed by a new
378
+ `oats.okf/knowledge-maintainer`, spawned by a trigger from the package
379
+ template `oats.okf:harvest-review`: a workspace file
380
+ (`oats-triggers/okf-harvest-review.yaml`) or a local `oats trigger add
381
+ --from oats.okf:harvest-review`.
382
+
383
+ Registering a source never installs the host timer. `oats okf inspect
384
+ --source <source.json> --soul <soul>` reports the job and whether the timer is
385
+ actually active; `oats okf setup --source <source.json> --soul <soul>
386
+ --install-host` installs it, and `--disable` disables the job without
387
+ stopping a running worker. The scheduler's launch receipts do not replace
388
+ oats.okf's own processing, delivery and acceptance receipts.