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