@aitne-sh/aitne 0.1.7 → 0.1.8

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 (222) hide show
  1. package/README.md +195 -829
  2. package/agent-assets/agent-profiles/_safety.md +49 -17
  3. package/agent-assets/agent-profiles/profile-importer.md +1 -1
  4. package/agent-assets/agent-profiles/routine.md +4 -3
  5. package/agent-assets/docs/concepts/agent-day.md +6 -1
  6. package/agent-assets/docs/concepts/auth-health.md +10 -1
  7. package/agent-assets/docs/concepts/backends-and-tiers.md +74 -40
  8. package/agent-assets/docs/concepts/costs-and-quotas.md +25 -5
  9. package/agent-assets/docs/concepts/delegated-mode.md +147 -68
  10. package/agent-assets/docs/concepts/memory-model.md +9 -4
  11. package/agent-assets/docs/concepts/observations.md +13 -1
  12. package/agent-assets/docs/concepts/process-keys.md +20 -5
  13. package/agent-assets/docs/concepts/routines.md +38 -20
  14. package/agent-assets/docs/concepts/safety-model.md +30 -13
  15. package/agent-assets/docs/concepts/skills.md +12 -7
  16. package/agent-assets/docs/features/integrations/calendar.md +1 -1
  17. package/agent-assets/docs/features/integrations/git.md +2 -2
  18. package/agent-assets/docs/features/integrations/github.md +9 -2
  19. package/agent-assets/docs/features/integrations/mail.md +1 -1
  20. package/agent-assets/docs/features/integrations/notion.md +34 -6
  21. package/agent-assets/docs/features/integrations/obsidian.md +7 -2
  22. package/agent-assets/docs/features/lifestyle/git.md +4 -7
  23. package/agent-assets/docs/features/lifestyle/receipts.md +17 -2
  24. package/agent-assets/docs/features/lifestyle/travel-bookings.md +15 -0
  25. package/agent-assets/docs/features/lifestyle/travel-time.md +7 -1
  26. package/agent-assets/docs/features/memory-files/agent-journal.md +2 -2
  27. package/agent-assets/docs/features/memory-files/projects.md +6 -0
  28. package/agent-assets/docs/features/memory-files/roadmap.md +5 -0
  29. package/agent-assets/docs/features/memory-files/today.md +1 -0
  30. package/agent-assets/docs/features/memory-files/user-profile.md +6 -0
  31. package/agent-assets/docs/features/messaging/bang-commands.md +20 -10
  32. package/agent-assets/docs/features/messaging/discord.md +12 -1
  33. package/agent-assets/docs/features/messaging/overview.md +10 -7
  34. package/agent-assets/docs/features/messaging/slack.md +13 -1
  35. package/agent-assets/docs/features/messaging/telegram.md +7 -1
  36. package/agent-assets/docs/features/messaging/whatsapp.md +12 -1
  37. package/agent-assets/docs/features/operations/activity-and-conversations.md +2 -2
  38. package/agent-assets/docs/features/operations/approvals.md +6 -0
  39. package/agent-assets/docs/features/operations/backend-routing.md +7 -0
  40. package/agent-assets/docs/features/operations/cost-tracking.md +6 -0
  41. package/agent-assets/docs/features/operations/notifications.md +6 -0
  42. package/agent-assets/docs/features/operations/schedule-approaching.md +22 -9
  43. package/agent-assets/docs/features/routines/custom-routines.md +10 -4
  44. package/agent-assets/docs/features/routines/evening-review.md +1 -1
  45. package/agent-assets/docs/features/routines/hourly-check.md +1 -1
  46. package/agent-assets/docs/features/routines/morning-routine.md +24 -15
  47. package/agent-assets/docs/features/routines/weekly-review.md +38 -12
  48. package/agent-assets/docs/features/wiki/commands.md +11 -0
  49. package/agent-assets/docs/features/wiki/overview.md +13 -3
  50. package/agent-assets/docs/getting-started/01-what-is-this.md +32 -11
  51. package/agent-assets/docs/getting-started/02-first-steps.md +17 -4
  52. package/agent-assets/docs/getting-started/03-what-can-this-do.md +21 -11
  53. package/agent-assets/docs/getting-started/04-first-day.md +14 -0
  54. package/agent-assets/docs/glossary.md +65 -12
  55. package/agent-assets/docs/guides/add-a-custom-routine.md +12 -0
  56. package/agent-assets/docs/guides/backup-and-restore.md +16 -2
  57. package/agent-assets/docs/guides/budget-and-cost-for-wiki.md +6 -0
  58. package/agent-assets/docs/guides/build-your-wiki.md +14 -0
  59. package/agent-assets/docs/guides/change-which-model-handles-x.md +7 -0
  60. package/agent-assets/docs/guides/connect-a-new-mail-account.md +16 -0
  61. package/agent-assets/docs/guides/explore-with-trace-and-connect.md +6 -0
  62. package/agent-assets/docs/guides/import-knowledge-file.md +11 -0
  63. package/agent-assets/docs/guides/install-and-run.md +20 -4
  64. package/agent-assets/docs/guides/maintain-wiki-health.md +6 -0
  65. package/agent-assets/docs/guides/migrate-machines.md +13 -1
  66. package/agent-assets/docs/guides/multiple-wikis-for-multiple-domains.md +9 -0
  67. package/agent-assets/docs/guides/pause-the-agent.md +12 -4
  68. package/agent-assets/docs/guides/reinstall-cleanly.md +19 -4
  69. package/agent-assets/docs/guides/setup-wizard.md +20 -9
  70. package/agent-assets/docs/guides/switch-default-backend.md +10 -1
  71. package/agent-assets/docs/guides/use-an-existing-obsidian-vault.md +5 -0
  72. package/agent-assets/docs/reference/api.md +29 -1
  73. package/agent-assets/docs/reference/cli-commands.md +22 -3
  74. package/agent-assets/docs/reference/config.md +37 -5
  75. package/agent-assets/docs/reference/disallowed-tools.md +13 -0
  76. package/agent-assets/docs/reference/keyboard-shortcuts.md +13 -0
  77. package/agent-assets/docs/reference/process-keys.md +70 -20
  78. package/agent-assets/docs/reference/skills.md +27 -9
  79. package/agent-assets/docs/troubleshooting/auth-failed.md +7 -2
  80. package/agent-assets/docs/troubleshooting/dashboard-shows-degraded.md +13 -1
  81. package/agent-assets/docs/troubleshooting/fallback-keeps-firing.md +10 -0
  82. package/agent-assets/docs/troubleshooting/messaging-not-pairing.md +11 -0
  83. package/agent-assets/docs/troubleshooting/morning-routine-didnt-run.md +9 -4
  84. package/agent-assets/docs/troubleshooting/observation-not-detected.md +12 -0
  85. package/agent-assets/docs/troubleshooting/quota-exhausted.md +7 -1
  86. package/agent-assets/docs/troubleshooting/wiki-ingest-full-blocked.md +5 -0
  87. package/agent-assets/docs/troubleshooting/wiki-write-failed.md +5 -0
  88. package/agent-assets/optimizer-skills/drift-analysis/SKILL.md +1 -1
  89. package/agent-assets/optimizer-skills/skill-curation/SKILL.md +2 -2
  90. package/agent-assets/skills/agent-actions/SKILL.md +122 -0
  91. package/agent-assets/skills/attach/SKILL.md +1 -2
  92. package/agent-assets/skills/context/SKILL.md +36 -454
  93. package/agent-assets/skills/context/references/api.md +220 -0
  94. package/agent-assets/skills/context/references/required-frontmatter.md +73 -0
  95. package/agent-assets/skills/context/references/snapshot-files.md +103 -0
  96. package/agent-assets/skills/context/seeds/file-responsibilities.seed.json +1 -1
  97. package/agent-assets/skills/docs-search/SKILL.md +13 -13
  98. package/agent-assets/skills/external-services/SKILL.delegated.claude.md +5 -7
  99. package/agent-assets/skills/external-services/SKILL.delegated.codex.md +5 -7
  100. package/agent-assets/skills/external-services/SKILL.delegated.gemini.md +5 -7
  101. package/agent-assets/skills/external-services/SKILL.md +6 -259
  102. package/agent-assets/skills/external-services/SKILL.native.claude.md +1 -2
  103. package/agent-assets/skills/external-services/SKILL.native.codex.md +1 -2
  104. package/agent-assets/skills/external-services/SKILL.native.gemini.md +1 -2
  105. package/agent-assets/skills/external-services/references/calendar-apple.md +97 -0
  106. package/agent-assets/skills/external-services/references/calendar-google.md +72 -0
  107. package/agent-assets/skills/external-services/references/calendar-outlook.md +36 -0
  108. package/agent-assets/skills/external-services/references/github.md +17 -0
  109. package/agent-assets/skills/external-services/references/obsidian.md +49 -0
  110. package/agent-assets/skills/external-services/references/skills-crud.md +27 -0
  111. package/agent-assets/skills/gmail-lifestyle/SKILL.md +224 -0
  112. package/agent-assets/skills/gmail-lifestyle/references/receipts-api.md +93 -0
  113. package/agent-assets/skills/gmail-lifestyle/references/travel-bookings-api.md +75 -0
  114. package/agent-assets/skills/gmail-lifestyle/references/travel-time-api.md +59 -0
  115. package/agent-assets/skills/mail/SKILL.delegated.claude.md +1 -1
  116. package/agent-assets/skills/mail/SKILL.delegated.codex.md +1 -1
  117. package/agent-assets/skills/mail/SKILL.delegated.gemini.md +1 -1
  118. package/agent-assets/skills/mail/SKILL.md +9 -114
  119. package/agent-assets/skills/mail/SKILL.native.claude.md +1 -1
  120. package/agent-assets/skills/mail/SKILL.native.codex.md +1 -1
  121. package/agent-assets/skills/mail/SKILL.native.gemini.md +1 -1
  122. package/agent-assets/skills/mail/references/api.md +108 -0
  123. package/agent-assets/skills/mail/references/examples.md +70 -0
  124. package/agent-assets/skills/mail/references/providers.md +8 -8
  125. package/agent-assets/skills/managed-tasks/SKILL.md +472 -0
  126. package/agent-assets/skills/managed-tasks/references/errors.md +70 -0
  127. package/agent-assets/skills/managed-tasks/references/output-path.md +75 -0
  128. package/agent-assets/skills/managed-tasks/references/recurrence-rule.md +86 -0
  129. package/agent-assets/skills/management-policy/SKILL.md +33 -105
  130. package/agent-assets/skills/management-policy/references/policy-workflow.md +101 -0
  131. package/agent-assets/skills/notify/SKILL.md +6 -78
  132. package/agent-assets/skills/notify/references/priority.md +60 -0
  133. package/agent-assets/skills/notion/SKILL.delegated.claude.md +1 -1
  134. package/agent-assets/skills/notion/SKILL.delegated.codex.md +1 -1
  135. package/agent-assets/skills/notion/SKILL.delegated.gemini.md +1 -1
  136. package/agent-assets/skills/notion/SKILL.md +6 -10
  137. package/agent-assets/skills/notion/SKILL.native.claude.md +1 -2
  138. package/agent-assets/skills/notion/SKILL.native.codex.md +1 -2
  139. package/agent-assets/skills/notion/SKILL.native.gemini.md +1 -2
  140. package/agent-assets/skills/observations/SKILL.md +1 -6
  141. package/agent-assets/skills/project-doc/SKILL.md +1 -5
  142. package/agent-assets/skills/reading/SKILL.md +2 -2
  143. package/agent-assets/skills/roadmap/SKILL.md +37 -135
  144. package/agent-assets/skills/roadmap/references/api.md +100 -0
  145. package/agent-assets/skills/roadmap/references/cross-check.md +73 -0
  146. package/agent-assets/skills/roadmap/references/migration.md +56 -0
  147. package/agent-assets/skills/roadmap/references/preparation-timeline.md +2 -2
  148. package/agent-assets/skills/schedule/SKILL.md +52 -88
  149. package/agent-assets/skills/schedule/references/batch.md +93 -0
  150. package/agent-assets/skills/schedule/references/errors.md +214 -0
  151. package/agent-assets/skills/schedule/references/model-selection.md +96 -0
  152. package/agent-assets/skills/schedule/references/recurrence-rule.md +86 -0
  153. package/agent-assets/skills/schedule/references/recurring.md +185 -0
  154. package/agent-assets/skills/scheduled-managed-task/SKILL.md +13 -15
  155. package/agent-assets/skills/today/SKILL.md +27 -57
  156. package/agent-assets/skills/today/references/agent-plan-lifecycle.md +113 -0
  157. package/agent-assets/skills/user-interview/SKILL.md +12 -59
  158. package/agent-assets/skills/user-interview/references/op-briefing.md +51 -0
  159. package/agent-assets/skills/user-interview/references/op-morning.md +59 -0
  160. package/agent-assets/skills/user-interview/references/sweep-and-fallback.md +1 -1
  161. package/agent-assets/skills/user-profile/SKILL.md +43 -63
  162. package/agent-assets/skills/user-profile/references/character-preferences.md +83 -0
  163. package/agent-assets/skills/user-profile/seeds/topic-files.seed.json +28 -0
  164. package/agent-assets/skills/wiki/wiki-ask/SKILL.md +0 -1
  165. package/agent-assets/skills/wiki/wiki-compile/SKILL.md +0 -1
  166. package/agent-assets/skills/wiki/wiki-connect/SKILL.md +0 -1
  167. package/agent-assets/skills/wiki/wiki-graduate/SKILL.md +0 -1
  168. package/agent-assets/skills/wiki/wiki-ingest/SKILL.md +0 -1
  169. package/agent-assets/skills/wiki/wiki-lint/SKILL.md +0 -1
  170. package/agent-assets/skills/wiki/wiki-trace/SKILL.md +0 -1
  171. package/agent-assets/skills/wiki/wiki-vault-rules/SKILL.md +0 -1
  172. package/agent-assets/system-prompts/routine-fetch-window.md +68 -0
  173. package/agent-assets/system-prompts/skill-index-instruction.md +26 -0
  174. package/agent-assets/task-flows/_partials/calendar-acquire.google_calendar.md +18 -11
  175. package/agent-assets/task-flows/_partials/calendar-acquire.outlook_calendar.md +16 -9
  176. package/agent-assets/task-flows/_partials/capture-user-info.md +24 -0
  177. package/agent-assets/task-flows/_partials/confirm-subflow.md +68 -0
  178. package/agent-assets/task-flows/_partials/dm-intent.long-horizon.md +35 -0
  179. package/agent-assets/task-flows/_partials/dm-intent.project.md +391 -0
  180. package/agent-assets/task-flows/_partials/mail-acquire.gmail.md +20 -11
  181. package/agent-assets/task-flows/_partials/mail-acquire.outlook_mail.md +17 -9
  182. package/agent-assets/task-flows/_partials/notion-acquire.notion.md +18 -12
  183. package/agent-assets/task-flows/knowledge.import.md +1 -1
  184. package/agent-assets/task-flows/message.received.dm.md +13 -15
  185. package/agent-assets/task-flows/message.received.dm_first.md +10 -14
  186. package/agent-assets/task-flows/routine.custom.md +3 -1
  187. package/agent-assets/task-flows/routine.evening_review.md +39 -163
  188. package/agent-assets/task-flows/routine.fetch_window.md +17 -12
  189. package/agent-assets/task-flows/routine.hourly_check.md +16 -8
  190. package/agent-assets/task-flows/routine.hourly_check.triage.md +1 -1
  191. package/agent-assets/task-flows/routine.monthly_review.md +46 -4
  192. package/agent-assets/task-flows/routine.morning_routine_journal.md +113 -0
  193. package/agent-assets/task-flows/routine.morning_routine_today.md +673 -0
  194. package/agent-assets/task-flows/routine.roadmap_refresh.md +60 -15
  195. package/agent-assets/task-flows/routine.user_profile_sweep.md +9 -10
  196. package/agent-assets/task-flows/routine.weekly_review.md +285 -70
  197. package/agent-assets/task-flows/scheduled.dm.md +8 -8
  198. package/agent-assets/task-flows/scheduled.task.md +5 -5
  199. package/agent-assets/task-flows/setup.initial.md +165 -245
  200. package/agent-assets/task-flows/wiki.ingest_url.md +1 -1
  201. package/agent-assets/templates/_manifest.json +7 -7
  202. package/agent-assets/templates/dossiers/_index.md +1 -1
  203. package/agent-assets/templates/rules/journal-format.md +145 -38
  204. package/agent-assets/templates/user/expertise.md +4 -2
  205. package/agent-assets/templates/user/goals.md +4 -2
  206. package/agent-assets/templates/user/people.md +8 -2
  207. package/agent-assets/templates/user/personal.md +4 -2
  208. package/agent-assets/templates/user/work.md +4 -2
  209. package/bin/aitne.mjs +8 -1
  210. package/package.json +4 -4
  211. package/scripts/commands/doctor.mjs +52 -0
  212. package/scripts/commands/run-now.mjs +202 -0
  213. package/scripts/commands/verify.mjs +264 -0
  214. package/agent-assets/docs/features/routines/monthly-review.md +0 -65
  215. package/agent-assets/skills/management-task-modify/SKILL.md +0 -203
  216. package/agent-assets/skills/management-task-register/SKILL.md +0 -330
  217. package/agent-assets/skills/management-task-stop/SKILL.md +0 -166
  218. package/agent-assets/skills/receipts/SKILL.md +0 -134
  219. package/agent-assets/skills/travel/SKILL.md +0 -132
  220. package/agent-assets/skills/travel-time/SKILL.md +0 -158
  221. package/agent-assets/task-flows/routine.morning_routine.md +0 -322
  222. package/agent-assets/task-flows/routine.morning_routine_initial.md +0 -204
@@ -7,14 +7,14 @@ Action line formats:
7
7
 
8
8
  ```
9
9
  - YYYY-MM-DD [tag]: description
10
- - completed YYYY-MM-DD: YYYY-MM-DD [tag]: description
10
+ - completed YYYY-MM-DD: YYYY-MM-DD [tag]: description
11
11
  ```
12
12
 
13
13
  The completed prefix date is the completion date; the second date is
14
14
  the original planned date. Preserve completed rows byte-for-byte across
15
15
  refreshes. Morning Routine marks an open row complete by rewriting:
16
16
  `- YYYY-MM-DD [tag]: foo` →
17
- `- completed <today>: YYYY-MM-DD [tag]: foo`.
17
+ `- completed <today>: YYYY-MM-DD [tag]: foo`.
18
18
 
19
19
  Tags: `[notify]`, `[today]`, `[check]`, `[schedule]`.
20
20
 
@@ -1,7 +1,6 @@
1
1
  ---
2
2
  name: schedule
3
3
  description: Load when scheduling a future agent wake-up, pre-composed DM, recurring task, or de-duping against existing pending schedules.
4
- when_to_use: Owns `/api/schedule`, `/api/schedule/dm`, and `/api/recurring-schedules`. `external-services` defers here for all time-based work.
5
4
  allowed-tools:
6
5
  - Bash(curl *)
7
6
  - Read
@@ -37,45 +36,12 @@ user but compound into duplicate DMs/notifications at fire time.
37
36
  regenerate on its own.
38
37
  4. **`confirm_dedup_key` check (mandatory for `confirm:` sub-flow rows
39
38
  only).** When scheduling a `dm_session` row with
40
- `taskContext.sub_flow="confirm"`, additionally filter
41
- `GET /api/schedule?status=pending,running` by
42
- `taskContext.confirm_dedup_key`. Skip if any match exists —
43
- regardless of `scheduledFor` distance or description match. The
44
- confirm sub-flow's chained-fire model (see `scheduled.dm.md`
45
- §"Confirmation follow-up" Step 3) and a hot-thread defer (Step 1
46
- check 1) both inherit the same dedup_key, so a successor or defer
47
- row legitimately occupies the queue with the same key — a second
48
- gate firing for the same topic MUST yield to it rather than
49
- double-asking.
50
-
51
- ```bash
52
- curl -s "http://localhost:8321/api/schedule?status=pending,running" \
53
- | jq --arg k "<gate>:<stable-topic>" \
54
- '[.items[] | select(.taskContext.confirm_dedup_key == $k)] | length'
55
- ```
56
-
57
- If the count is `≥ 1`, log to `## Agent Log` and proceed without
58
- scheduling:
59
- ```
60
- - HH:MM [confirm] skipped <dedup_key>: row already pending
61
- ```
62
-
63
- **`confirm_dedup_key` shape contract.** The key is
64
- `<gate>:<stable-topic>` — for example
65
- `create_project:la-pm-masters`,
66
- `roadmap_ambiguous:tokyo-trip-date`,
67
- `managed_task_dedup:<existing-task-id>`. The gate scope ensures
68
- two unrelated gates can't collide on the same topic name; the
69
- topic component MUST be deterministic from the topic itself
70
- (no timestamps, no message IDs, no random nonces) so re-fires of
71
- the same gate produce the same key and this pre-flight catches
72
- them.
73
-
74
- This rule layers on top of bullets 1-3, which catch the common
75
- recurring / Agent-Plan duplicates. Bullet 4 catches the case
76
- where two confirms target the same topic at different scheduled
77
- times (e.g. one queued for the morning briefing, another the
78
- gate would queue for `+4h`).
39
+ `taskContext.sub_flow="confirm"`, run the dedup pre-check + shape
40
+ contract documented in
41
+ `task-flows/_partials/confirm-subflow.md` (also included verbatim
42
+ by `scheduled.dm.md` and `message.received.dm{,_first}.md`). The
43
+ single source covers the `dedup_key` filter, the
44
+ `<gate>:<stable-topic>` shape, and cross-path cancellation.
79
45
 
80
46
  Log the skip to `## Agent Log`:
81
47
  `- HH:MM [schedule] skipped <subject>: duplicate of <planId|row>`.
@@ -144,9 +110,11 @@ month about ESTA for the LA trip"), either write/promote the roadmap
144
110
  item via the roadmap skill and let AAP schedule the reminder, or call
145
111
  `/api/schedule/dm` with `"importance":"strategic"`.
146
112
 
147
- ## Model selection
148
- - **`sonnet`** (default) — reminders, notifications, single-step tasks
149
- - **`opus`**multi-file analysis, cross-repo reasoning, decisions affecting user commitments
113
+ ## Tier / Model selection
114
+
115
+ Pick `tier` (`lite` / `medium` / `high`) by default backend-neutral cost knob. Pin `model` (registered id, alias, or `<backendId>/<modelId>`) only when the row must outlive a `/settings/models` re-route. Mutually exclusive — both set returns `schedule.tier_and_model_conflict`. Omit both to use the dispatcher's process-key default. Discovery, PATCH swap form, alias rewrite, and `/api/schedule/options` payload are in the reference below.
116
+
117
+ {{> ref:model-selection }}
150
118
 
151
119
  ## Time discipline
152
120
  - **Absolute time required** — resolve relative expressions via `<current_time>` into ISO 8601 with offset. E.g. "in 1 hour" at 15:30 EDT → `2026-04-06T16:30:00-04:00`.
@@ -181,7 +149,7 @@ Response: `{ "status":"scheduled", "scheduleId":"123", "scheduledFor":"..." }`.
181
149
  ```bash
182
150
  curl -s -X POST http://localhost:8321/api/schedule \
183
151
  -H 'Content-Type: application/json' \
184
- -d '{"time":"2026-04-06T16:00:00-04:00","taskType":"wake","description":"Check PR #42 status and notify user.","model":"sonnet","taskContext":{"scheduledBy":"dm_conversation"}}'
152
+ -d '{"time":"2026-04-06T16:00:00-04:00","taskType":"wake","description":"Hourly docker health check: run `docker ps --format` and DM if any container is in restart loop.","tier":"lite","taskContext":{"scheduledBy":"docker_monitor"}}'
185
153
  ```
186
154
  | Field | Required | Description |
187
155
  |---|---|---|
@@ -189,7 +157,8 @@ curl -s -X POST http://localhost:8321/api/schedule \
189
157
  | `taskType` | Yes | `wake` for scheduled tasks |
190
158
  | `description` | Yes | Self-contained (min 20 chars). See format above. Doubles as the agent body unless `prompt` overrides it. |
191
159
  | `prompt` | No | Optional override for the agent body (min 20 chars when set). When set, the dispatcher injects this — not `description` — into the task-flow template. Use when you want a short list-friendly `description` plus a longer, separate instruction for the agent. |
192
- | `model` | No | `sonnet` (default) or `opus` |
160
+ | `tier` | No | `lite` / `medium` / `high`. Omit to use the dispatcher's process-key default (medium for `scheduled.task`). See "Tier / Model selection" above. Mutually exclusive with `model`. |
161
+ | `model` | No | Registered model id (`claude-opus-4-7`, `gpt-5.4`, …), legacy alias (`sonnet` / `opus`, auto-rewritten to `tier`), or composite `<backendId>/<modelId>`. See "Tier / Model selection" above. Mutually exclusive with `tier`. |
193
162
  | `taskContext` | No | Structured metadata object |
194
163
 
195
164
  Response: `{ "status":"scheduled", "scheduleId":"123", "scheduledFor":"YYYY-MM-DD HH:MM:SS" }`. `scheduledFor` is the normalized UTC SQLite timestamp the daemon actually stored — log this verbatim instead of re-formatting the input `time`. Rejects times in the past (> 1 min ago), same as `/api/schedule/dm`.
@@ -200,8 +169,7 @@ curl -s -X PATCH http://localhost:8321/api/schedule/42 \
200
169
  -H 'Content-Type: application/json' \
201
170
  -d '{"time":"2026-04-06T17:00:00-04:00"}'
202
171
  ```
203
- Fields: `time` (ISO 8601), `description` (min 20 chars, non-dm only), `prompt` (min 20 chars OR `null` to clear; non-dm only), `message` (dm only), `model`, `taskContext`. At least one required. Only `pending` items editable. `description`/`message` mutually exclusive; `prompt`/`message` mutually exclusive.
204
- Response: `{ "status":"updated", "id":42 }` / 404 / 409 (not pending).
172
+ Fields: `time` (ISO 8601), `description` (min 20 chars, non-dm only), `prompt` (min 20 chars OR `null` to clear; non-dm only), `message` (dm only), `tier` (`lite`/`medium`/`high` OR `null` to clear), `model` (registered id / alias / composite OR `null` to clear), `taskContext`. At least one required. Only `pending` items editable. `description`/`message` mutually exclusive; `prompt`/`message` mutually exclusive. Tier ↔ model swap form is in the model-selection reference above. Response: `{ "status":"updated", "id":42, "warnings":[] }` / 404 / 409 — surface `warnings[]` (e.g. `schedule.model_deprecated`) to the next turn.
205
173
 
206
174
  ### GET /api/schedule — List scheduled items
207
175
  ```bash
@@ -211,7 +179,7 @@ Param `status` (default `pending,running`): comma-separated `pending`, `running`
211
179
  Param `roadmapEligible=true`: return only rows that may become
212
180
  roadmap `Scheduled:` entries (`transient` / `low` excluded, `normal`
213
181
  only beyond 7 days, `strategic` included).
214
- Response: `{ "items":[{ "id","scheduledFor","taskType","description","prompt","status","model","taskContext","createdAt" }] }` `prompt` is `null` when no override is set. `taskContext` is the parsed JSON object (or `null` when none was stored); use it for sub-flow-aware filtering, e.g. `jq '.items[] | select(.taskContext.confirm_dedup_key == "create_project:la-pm-masters")'`.
182
+ Response: `{ "items":[{ "id","scheduledFor","taskType","description","prompt","status","model","backendId","tier","taskContext","createdAt" }] }`. `prompt` / `tier` / `model` / `backendId` are `null` when no override is set. `model` is a registered id verbatim and travels with `backendId` when set — the row carries either the `(model, backendId)` pin or `tier`, never both. Legacy alias inputs (`sonnet` / `opus`) are normalized to `tier` at write time. `taskContext` is the parsed JSON (or `null`); filter with `jq` e.g. `'.items[] | select(.taskContext.confirm_dedup_key == "create_project:la-pm-masters")'`.
215
183
 
216
184
  ### DELETE /api/schedule/:id — Cancel a pending item
217
185
  ```bash
@@ -219,51 +187,47 @@ curl -s -X DELETE http://localhost:8321/api/schedule/42
219
187
  ```
220
188
  Only cancels `pending` items. Response: `{ "status":"cancelled", "id":42 }` / 404 / 409.
221
189
 
190
+ ### POST /api/schedule/batch — Bulk register rich-context schedules
191
+
192
+ Morning-routine Stage A only. Single-row callers use `POST /api/schedule`
193
+ above. The required `taskContext.background` + `expected_output`
194
+ fields, the 50-row cap, the atomic / per-row commit modes, and the
195
+ success payload are in the batch reference below.
196
+
197
+ {{> ref:batch }}
198
+
222
199
  ---
223
200
 
224
- ## Recurring Schedules
225
- For tasks that repeat on a fixed pattern. Auto-regenerates the next occurrence after each execution.
201
+ ## Errors
226
202
 
227
- ### POST /api/recurring-schedules Create
228
- ```bash
229
- # Daily at 09:00
230
- curl -s -X POST http://localhost:8321/api/recurring-schedules \
231
- -H 'Content-Type: application/json' \
232
- -d '{"taskType":"wake","description":"Morning inbox triage — check pending observations and update today.md.","recurrenceRule":{"frequency":"daily","time":"09:00"}}'
233
- # Weekly Mon/Wed/Fri at 10:00
234
- curl -s -X POST http://localhost:8321/api/recurring-schedules \
235
- -H 'Content-Type: application/json' \
236
- -d '{"taskType":"wake","description":"Standup prep — review PRs, calendar, and blockers.","recurrenceRule":{"frequency":"weekly","time":"10:00","daysOfWeek":[1,3,5]}}'
237
- ```
238
- | Field | Required | Description |
239
- |---|---|---|
240
- | `taskType` | Yes | Task type for dispatch (e.g. `wake`) |
241
- | `description` | Yes | Self-contained (min 20 chars). Same rules as one-shot. Doubles as the agent body unless `prompt` overrides it. |
242
- | `prompt` | No | Optional override for the agent body (min 20 chars when set). Each materialized one-shot row inherits this from the recurring parent. |
243
- | `recurrenceRule` | Yes | `{ frequency, time, timezone?, daysOfWeek?, daysOfMonth? }` |
244
- | `model` | No | `sonnet` (default) or `opus` |
245
- | `taskContext` | No | Structured metadata object |
203
+ Every endpoint in this skill emits errors in the **agent-consumable
204
+ envelope** — read `errors[].hint`, fix the value at `errors[].field`,
205
+ and resubmit the same body. The full envelope shape and every
206
+ `schedule.*` code (request-shape, time-bound, row-content, taskContext,
207
+ model, batch) are in the errors reference below.
246
208
 
247
- **Recurrence rule:** `frequency`: `daily`/`weekly`/`monthly`. `time`: `HH:MM` local. `timezone`: IANA (auto-filled from daemon config). `daysOfWeek` (weekly): 0=Sun..6=Sat. `daysOfMonth` (monthly): 1-31 (31 clamps to month end).
209
+ {{> ref:errors }}
248
210
 
249
- Response: `{ "status":"created", "item":{ "id","recurrenceLabel","nextRunAt",... } }`
211
+ ---
250
212
 
251
- ### GET /api/recurring-schedules — List
252
- ```bash
253
- curl -s "http://localhost:8321/api/recurring-schedules?enabled=true"
254
- ```
255
- Response: `{ "items":[{ "id","taskType","description","recurrenceRule","enabled","nextRunAt","recurrenceLabel" }] }`
213
+ ## Recurring Schedules
256
214
 
257
- ### PATCH /api/recurring-schedules/:id Update
258
- ```bash
259
- curl -s -X PATCH http://localhost:8321/api/recurring-schedules/1 \
260
- -H 'Content-Type: application/json' \
261
- -d '{"recurrenceRule":{"frequency":"daily","time":"10:00"}}'
262
- ```
263
- Updatable: `recurrenceRule`, `description`, `prompt` (string sets an override, `null` clears), `model`, `taskContext`, `enabled`. Changing `recurrenceRule`/`enabled` auto-reschedules. Set `{"enabled":false}` to pause.
215
+ For tasks that repeat on a fixed pattern. The daemon auto-regenerates
216
+ the next one-shot occurrence after each execution. **Hourly / daily /
217
+ weekly / monthly** cadences are supported; the recurring reference
218
+ below documents the full shape, the hourly + monthly missing-day
219
+ recipes, pause-vs-delete trade-off, and PATCH / GET / DELETE surface.
264
220
 
265
- ### DELETE /api/recurring-schedules/:id — Delete
266
- ```bash
267
- curl -s -X DELETE http://localhost:8321/api/recurring-schedules/1
268
- ```
269
- Deletes schedule and cancels pending instances. Response: `{ "status":"deleted", "id":1 }`
221
+ {{> ref:recurring }}
222
+
223
+ ### recurrenceRule grammar engine vs consumer
224
+
225
+ The full engine grammar (mapping table, frequency-vs-field matrix,
226
+ cadence-string-must-match-recurrenceRule discipline) is shared with
227
+ the `managed-tasks` skill. The reference is byte-identical across
228
+ both skills — pinned by `skills-manifest.test.ts` so they cannot
229
+ drift. Schedule callers may use any of the four frequencies the
230
+ engine accepts; the `managed-tasks` consumer chooses to refuse
231
+ sub-daily for app-fetch correctness and that constraint lives there.
232
+
233
+ {{> ref:recurrence-rule }}
@@ -0,0 +1,93 @@
1
+ ---
2
+ kind: reference
3
+ name: batch
4
+ description: POST /api/schedule/batch — bulk register up to 50 rich-context schedules in one atomic transaction. Morning-routine Stage A is the primary caller.
5
+ ---
6
+
7
+ # POST /api/schedule/batch — Bulk register rich-context schedules
8
+
9
+ Used by the morning-routine Stage A to register every same-day
10
+ schedule in one atomic transaction. Each row's `taskContext` MUST
11
+ carry the context a future `scheduled.task` / `scheduled.dm` session
12
+ needs to produce high-quality output hours later — the daemon cannot
13
+ reconstruct this from the user-facing description.
14
+
15
+ If you are not the morning routine, you almost certainly want
16
+ `POST /api/schedule` (single-row) instead — batch's required
17
+ `taskContext.background` + `expected_output` fields are overkill for
18
+ one-off DM-handler reminders.
19
+
20
+ ## Example
21
+
22
+ ```bash
23
+ curl -s -X POST http://localhost:8321/api/schedule/batch \
24
+ -H 'Content-Type: application/json' \
25
+ -d '{
26
+ "rows": [
27
+ {
28
+ "scheduledFor": "2026-05-15T14:30:00-04:00",
29
+ "taskType": "wake",
30
+ "taskDescription": "Pre-brief the 15:00 standup with the two open Q2 risks.",
31
+ "taskContext": {
32
+ "background": "User flagged Q2 roadmap risks in yesterdays DM; standup needs the two open items front-loaded so the team aligns before 15:30.",
33
+ "expected_output": "DM with two bullet items + one suggested mitigation each, sent 30min before standup.",
34
+ "references": ["projects/q2-roadmap.md#open-risks", "calendar:event:standup-2026-05-15"],
35
+ "tone": "concise"
36
+ }
37
+ }
38
+ ],
39
+ "atomic": true
40
+ }'
41
+ ```
42
+
43
+ ## Fields
44
+
45
+ | Field | Required | Description |
46
+ |---|---|---|
47
+ | `rows` | Yes | Array of row objects (max 50 per batch). Empty array is a documented no-op. |
48
+ | `rows[].scheduledFor` | Yes | ISO 8601 with timezone offset. Must be >= 1 minute in the future. |
49
+ | `rows[].taskType` | Yes | `wake` / `dm_session` / `check` / `dm`. |
50
+ | `rows[].taskDescription` | Yes | Self-contained (min 20 chars). Doubles as the agent body unless `taskPrompt` overrides. |
51
+ | `rows[].taskContext.background` | Yes | Why this task is being scheduled (min 30 chars). Anchor for the future session. |
52
+ | `rows[].taskContext.expected_output` | Yes | What the future session should produce (min 20 chars). |
53
+ | `rows[].taskContext.references` | No | Stable handles the future session can look up (project paths, calendar event ids). |
54
+ | `rows[].taskContext.tone` | No | Free-form tone hint for DM-shaped output. |
55
+ | `rows[].taskContext.tier_override` | No | `lite` / `medium` / `high` / `null`. **Legacy slot — prefer `rows[].tier` (top-level)**. When `tier` is omitted, this value is lifted into the row's `tier_override` column at insert time. |
56
+ | `rows[].tier` | No | `lite` / `medium` / `high`. Abstract cost knob — primary path. Wins over `taskContext.tier_override` when both are set. Mutually exclusive with `rows[].model` on the same row. |
57
+ | `rows[].taskContext.sub_flow` | No | Branches the task-flow rendering when the dispatcher needs a specialised sub-flow. |
58
+ | `rows[].taskPrompt` | No | Override for the agent body (min 20 chars when set). |
59
+ | `rows[].correlationId` | No | Defaults to the morning routine's correlation id when omitted. |
60
+ | `rows[].model` | No | Registered model id (`claude-opus-4-7`, `claude-sonnet-4-6`, `gpt-5.4`, `gemini-3.1-pro-preview`, …), legacy alias (`sonnet` / `opus` — auto-rewritten to `tier`), composite `<backendId>/<modelId>`, or `null`. Mutually exclusive with `rows[].tier`. Omit both to let `process_backend_config` decide. |
61
+ | `atomic` | No | `true` (default) wraps inserts in one transaction — any row error rolls back all. `false` commits successful rows individually. |
62
+
63
+ ## Success
64
+
65
+ 201:
66
+
67
+ ```json
68
+ { "ok": true, "rowsAttempted": 1, "rowsCommitted": 1, "ids": [101], "warnings": [] }
69
+ ```
70
+
71
+ `warnings[]` carries non-blocking advisories (per-row issues like
72
+ `schedule.model_deprecated` keep the rowIndex so the agent can map
73
+ warnings back to the offending row). Rows are still committed —
74
+ surface the warnings to the next turn so the LLM can refine without
75
+ re-POSTing.
76
+
77
+ ## Errors
78
+
79
+ Returns the standard agent-consumable envelope — see
80
+ `references/errors.md`. `rowsCommitted` tells you how much of the
81
+ batch landed; with `atomic:true` any error means `rowsCommitted === 0`.
82
+ Per-row `model_unknown` / `model_ambiguous` / `tier_and_model_conflict`
83
+ all reach this envelope with `rowIndex` set — fix the offending rows
84
+ and resubmit the same body.
85
+
86
+ ## When NOT to use batch
87
+
88
+ - One-off DM-handler reminders → use `POST /api/schedule` (single
89
+ row, no required `taskContext.background`).
90
+ - DM-tone scheduled messages → use `POST /api/schedule/dm` (no agent
91
+ invoked at fire time).
92
+ - More than 50 rows in a single horizon → chunk into multiple
93
+ `atomic:true` batches; do not raise the cap.
@@ -0,0 +1,214 @@
1
+ ---
2
+ kind: reference
3
+ name: errors
4
+ description: Agent-consumable error envelope shape + every `schedule.*` code emitted by /api/schedule and /api/schedule/batch.
5
+ ---
6
+
7
+ # Schedule error envelope + codes
8
+
9
+ Every endpoint in this skill emits errors in the
10
+ **agent-consumable envelope** so you can self-correct in the same
11
+ turn instead of retrying blindly:
12
+
13
+ ```jsonc
14
+ {
15
+ "ok": false,
16
+ "summary": "1 validation error. Fix the listed errors and retry.",
17
+ "errors": [
18
+ {
19
+ "rowIndex": 2, // null when not a batch row
20
+ "code": "schedule.task_context_field_missing", // stable machine code
21
+ "field": "rows[2].taskContext.background", // JSON-pointer-ish path
22
+ "received": "<missing>",
23
+ "expected": "string with >= 30 characters explaining why this task is being scheduled",
24
+ "constraint": { "type": "string", "minLength": 30, "required": true },
25
+ "validValues": null, // runtime-derived set, when applicable (see "validValues vs constraint.enum")
26
+ "hint": "Stage A must populate taskContext.background so the future session can produce high-quality output without re-deriving context. Example: ...",
27
+ "skillAnchor": "schedule#taskContext-required-fields",
28
+ "docsUrl": "agent-assets/skills/schedule/references/errors.md#task_context_field_missing",
29
+ "severity": "error"
30
+ }
31
+ ],
32
+ "warnings": [], // non-blocking advisories — see "Warnings channel"
33
+ "retryable": true,
34
+ "retryHint": "Fix the listed rows and POST the same body again. atomic=true (the default) means no rows were committed."
35
+ }
36
+ ```
37
+
38
+ When you see an error: read `errors[].hint`, fix the value at
39
+ `errors[].field`, and resubmit the same body. The morning-routine
40
+ task-flow gates batch retries on `rowsCommitted === rows.length`; do
41
+ not retry a row-level fix on a different field path.
42
+
43
+ ## Issue fields
44
+
45
+ | Field | Use |
46
+ |---|---|
47
+ | `code` | Stable namespaced identifier. Switch on this in skill prose, not on `expected` or `hint`. |
48
+ | `field` | JSON-pointer-ish path to the offending input (`rows[2].taskContext.background`). |
49
+ | `received` | Exact value the daemon saw. `'<missing>'` sentinel when the field was omitted. |
50
+ | `expected` | One-sentence summary of what would have been accepted. |
51
+ | `constraint` | Static, schema-level shape (`{type, minLength, enum: [...]}`). Fixed across deploys. |
52
+ | `validValues` | Runtime-derived list of acceptable values — populated when the answer is data the operator can change (model registry, IANA timezones, an integration's supported modes). Distinct from `constraint.enum`: never both on the same code. |
53
+ | `hint` | Concrete remediation guidance with an example. |
54
+ | `skillAnchor` | `<skill>#<slug>` reference for fuller context — `Read agent-assets/skills/<skill>/SKILL.md#<slug>`. |
55
+ | `docsUrl` | Repo-relative path to deeper "what to do" prose, including a fragment that lands on the code's heading in this file. |
56
+ | `severity` | `error` blocks the commit; `warning` is advisory only (also surfaced via `warnings[]` — see below). |
57
+
58
+ ### validValues vs constraint.enum
59
+
60
+ These two fields look alike but answer different questions:
61
+
62
+ - **`constraint.enum`** — schema-level static list (`["lite","medium","high"]`, `["hourly","daily","weekly","monthly"]`). Same on every deploy.
63
+ - **`validValues`** — runtime-derived list (the model registry snapshot, which evolves as new models are registered; the IANA timezone set; an integration's `supportedModes`). Filled by the route at error-time.
64
+
65
+ Use `validValues` when present — it reflects what the daemon will accept on this run, including any newly added entries. `constraint.enum` is the
66
+ specification-time guarantee. The two never appear together on the same code.
67
+
68
+ ## Warnings channel
69
+
70
+ Some inputs are syntactically valid but suspicious enough to flag —
71
+ deprecated model on a long-lived recurring rule, `daysOfMonth:[31]`
72
+ with the default `lastDayOfMonth` policy, etc. The daemon does **not**
73
+ reject these; the row is persisted and the response returns 200/201
74
+ with a `warnings: []` array using the same issue shape as `errors[]`:
75
+
76
+ ```jsonc
77
+ {
78
+ "status": "created",
79
+ "item": { "id": 42, "recurrenceRule": { ... }, "nextRunAt": "2026-05-31T12:00:00Z" },
80
+ "warnings": [
81
+ {
82
+ "rowIndex": null,
83
+ "code": "schedule.on_missing_day_unused",
84
+ "field": "recurrenceRule.onMissingDay",
85
+ "received": "lastDayOfMonth",
86
+ "expected": "onMissingDay only matters when daysOfMonth contains 29, 30, or 31",
87
+ "hint": "Drop onMissingDay or add 29/30/31 to daysOfMonth.",
88
+ "skillAnchor": "schedule#monthly-missing-day",
89
+ "severity": "warning"
90
+ }
91
+ ]
92
+ }
93
+ ```
94
+
95
+ Surface warnings to the next agent turn (e.g. include them in the
96
+ DM that confirms the schedule was created) so the LLM can refine on
97
+ the next call if the warning matters. **Don't treat warnings as
98
+ failures** — they are advisory, not blocking. `retryable` is computed
99
+ from `errors[]` only and ignores `warnings[]`.
100
+
101
+ When the same envelope contains both `errors` and `warnings`, the
102
+ errors path runs first: fix every entry in `errors[]`, then inspect
103
+ `warnings[]` on the retried response.
104
+
105
+ ## Codes the schedule endpoints can emit
106
+
107
+ ### Request-shape codes
108
+
109
+ Apply to `POST /api/schedule` and `POST /api/schedule/batch`.
110
+
111
+ <a id="request-shape"></a>
112
+
113
+ | Code | When | Fix |
114
+ |---|---|---|
115
+ | <a id="body_not_object"></a> `schedule.body_not_object` | Body is not a JSON object. | POST `{"rows":[…]}` for batch, or the row fields directly for single-row. |
116
+ | <a id="rows_field_missing"></a> `schedule.rows_field_missing` | Batch body is missing the `rows` array. | Wrap your row objects in a `rows` array. |
117
+ | <a id="rows_too_many"></a> `schedule.rows_too_many` | Batch contains > 50 rows. | Split into chunks of at most 50 rows. |
118
+ | <a id="batch_atomic_invalid"></a> `schedule.batch_atomic_invalid` | `atomic` is not a boolean. | Pass `true` / `false`, or omit (defaults to `true`). |
119
+
120
+ ### Time-bound codes
121
+
122
+ <a id="scheduledFor-bounds"></a>
123
+
124
+ | Code | When | Fix |
125
+ |---|---|---|
126
+ | <a id="scheduled_for_invalid"></a> `schedule.scheduled_for_invalid` | `scheduledFor` / `time` is not parseable by `Date()`. | Use ISO 8601 with timezone offset. Resolve relative times via `<current_time>`. |
127
+ | <a id="scheduled_for_in_past"></a> `schedule.scheduled_for_in_past` | `scheduledFor` is earlier than now (with a 1-minute grace). | Pick a future time. Inspect `<current_time>` and pick now+1min minimum. |
128
+
129
+ ### Row-content codes
130
+
131
+ <a id="taskType"></a>
132
+ <a id="description-shape"></a>
133
+
134
+ | Code | When | Fix |
135
+ |---|---|---|
136
+ | <a id="task_type_unknown"></a> `schedule.task_type_unknown` | `taskType` is not `wake` / `dm_session` / `check` / `dm`. | Pick the matching type. Use `/api/schedule/dm` for the precomposed-DM variant. |
137
+ | <a id="description_too_short"></a> `schedule.description_too_short` | `description` / `taskDescription` < 20 chars. | Expand the description so the wake-up agent has enough context to act. |
138
+ | <a id="prompt_too_short"></a> `schedule.prompt_too_short` | `prompt` / `taskPrompt` is set but < 20 chars. | Either remove it (description doubles as the body) or expand it. |
139
+
140
+ ### taskContext required fields
141
+
142
+ <a id="taskContext-required-fields"></a>
143
+
144
+ For `POST /api/schedule/batch`, every row's `taskContext` must carry
145
+ `background` (>=30 chars) and `expected_output` (>=20 chars). The
146
+ future session firing at the scheduled time inherits these verbatim
147
+ — its output quality is bounded by the richness of what you write
148
+ here.
149
+
150
+ | Code | When | Fix |
151
+ |---|---|---|
152
+ | <a id="task_context_field_missing"></a> `schedule.task_context_field_missing` | `taskContext.background` or `taskContext.expected_output` is absent. | Populate both. `background` explains *why* this row exists; `expected_output` defines what "done" looks like. |
153
+ | <a id="task_context_field_too_short"></a> `schedule.task_context_field_too_short` | One of the required taskContext fields is below its min length. | Expand the string. Trivial values like "follow up" don't survive a 4-hour gap. |
154
+ | <a id="task_context_field_wrong_type"></a> `schedule.task_context_field_wrong_type` | A typed taskContext slot received the wrong type (e.g. `references` is a string instead of `string[]`). | Match the schema: references is `string[]`, tier_override is `null|"lite"|"medium"|"high"`, tone is a free string. |
155
+
156
+ ### Model selection
157
+
158
+ <a id="model-selection"></a>
159
+ <a id="tier-selection"></a>
160
+ <a id="tier-vs-model"></a>
161
+
162
+ `model` accepts a free-form token after SCHEDULE_API_REDESIGN_PLAN
163
+ §4.3: legacy aliases (`sonnet` / `opus` — rewritten to `tier` at
164
+ the route), full registered model ids (e.g. `claude-opus-4-7`,
165
+ `gpt-5.4`), or the composite `<backendId>/<modelId>` form when an
166
+ id appears under multiple backends. `tier` (`lite` | `medium` |
167
+ `high`) is the abstract cost knob and is mutually exclusive with
168
+ `model`. Prefer `tier` for new schedules — the dispatcher picks the
169
+ latest non-deprecated model per resolved process key automatically.
170
+
171
+ | Code | When | Fix |
172
+ |---|---|---|
173
+ | <a id="model_unknown"></a> `schedule.model_unknown` | `model` is not a registered alias / model id. | Inspect `validValues.aliases` and `validValues.models` on the response — these list every value the daemon will accept right now. Omit `model` to let `process_backend_config` decide. |
174
+ | <a id="model_ambiguous"></a> `schedule.model_ambiguous` | `model` matches more than one backend in the registry. | Resubmit using the composite `<backendId>/<modelId>` form (see `validValues.matches`). |
175
+ | <a id="model_deprecated"></a> `schedule.model_deprecated` (warning) | `model` is registered but flagged deprecated. | The row was still created. Switch to a non-deprecated id from `validValues.availableModels`, or use `tier` instead. |
176
+ | <a id="backend_id_unknown"></a> `schedule.backend_id_unknown` | Backend portion of the composite token is not `claude` / `codex` / `gemini` / `opencode`. | Use one of the four BackendId values. |
177
+ | <a id="tier_unknown"></a> `schedule.tier_unknown` | `tier` is not `lite` / `medium` / `high`. | Pick one of the three tiers or omit entirely. |
178
+ | <a id="tier_and_model_conflict"></a> `schedule.tier_and_model_conflict` | Both `tier` AND `model` set on the same row. | Pick exactly one: `tier` (recommended) OR `model`. On PATCH you can clear one and set the other in the same request (pass `null` to clear). |
179
+
180
+ ### Batch-shape codes
181
+
182
+ <a id="batch-shape"></a>
183
+
184
+ See the table under "Request-shape codes" above. `rowsAttempted` /
185
+ `rowsCommitted` in the envelope tell you how much of the batch
186
+ committed; with `atomic:true` (the default) every error means
187
+ `rowsCommitted === 0`.
188
+
189
+ ### Recurring-schedules (`/api/recurring-schedules`)
190
+
191
+ <a id="recurring-shape"></a>
192
+
193
+ POST and PATCH `/api/recurring-schedules` route every Zod validation
194
+ failure through `translateZodError` so each offending field surfaces
195
+ as its own code instead of collapsing onto one
196
+ `recurring_schedules.validation_error` issue. The codes below mirror
197
+ the per-frequency rules in `recurrence-rule.md`.
198
+
199
+ | Code | When | Fix |
200
+ |---|---|---|
201
+ | <a id="frequency_unknown"></a> `schedule.frequency_unknown` | `recurrenceRule.frequency` not in the enum. | Pick `hourly` / `daily` / `weekly` / `monthly`. |
202
+ | <a id="frequency_field_mismatch"></a> `schedule.frequency_field_mismatch` | Wrong fields for the chosen frequency (e.g. `time` on `hourly`, `daysOfWeek` on `daily`). | See `validValues.requiredFor` / `forbiddenFor` for the exact matrix. |
203
+ | <a id="interval_hours_out_of_range"></a> `schedule.interval_hours_out_of_range` | `intervalHours` outside `[1, 23]`. | Use 1..23; for daily switch frequency. |
204
+ | <a id="minute_of_hour_out_of_range"></a> `schedule.minute_of_hour_out_of_range` | `minuteOfHour` outside `[0, 59]`. | Pick 0..59 (default 0). |
205
+ | <a id="time_format_invalid"></a> `schedule.time_format_invalid` | `time` not `HH:MM` 24h. | Use the exact form `09:00` / `21:30`. |
206
+ | <a id="days_of_week_invalid"></a> `schedule.days_of_week_invalid` | `daysOfWeek` empty, duplicate, or out of `[0, 6]`. | 0=Sun..6=Sat, distinct entries only. |
207
+ | <a id="days_of_month_invalid"></a> `schedule.days_of_month_invalid` | `daysOfMonth` empty, duplicate, or out of `[1, 31]`. | 1..31, distinct entries only — use `onMissingDay` to control 29-31 behavior. |
208
+ | <a id="on_missing_day_unknown"></a> `schedule.on_missing_day_unknown` | `onMissingDay` not `skip` / `lastDayOfMonth`. | Pick one (default `lastDayOfMonth`). |
209
+ | <a id="on_missing_day_unused"></a> `schedule.on_missing_day_unused` (warning) | `onMissingDay` set but `daysOfMonth` has no entry in `[29, 30, 31]`. | Advisory — row is created. Either drop `onMissingDay` (no effect on a 1..28 set) or extend `daysOfMonth` to include 29/30/31 if you meant a month-end rule. |
210
+ | <a id="timezone_unknown"></a> `schedule.timezone_unknown` | `timezone` is not a valid IANA zone. | Use a real zone (`Asia/Tokyo`, `America/New_York`, `UTC`). |
211
+ | <a id="recurrence_rule_invalid"></a> `schedule.recurrence_rule_invalid` | `recurrenceRule` is structurally invalid in a way the traversal could not localise. | Inspect the response `field` path and resubmit a well-formed object. |
212
+ | <a id="recurring_id_invalid"></a> `schedule.recurring_id_invalid` | id segment not a positive integer. | Use the `item.id` returned by POST. |
213
+ | <a id="recurring_not_found"></a> `schedule.recurring_not_found` | No row with this id. | List `/api/recurring-schedules` to see current rows. |
214
+ | <a id="recurring_no_changes"></a> `schedule.recurring_no_changes` | PATCH body is empty. | Supply at least one of `description` / `prompt` / `recurrenceRule` / `model` / `tier` / `taskContext` / `enabled`. |
@@ -0,0 +1,96 @@
1
+ ---
2
+ kind: reference
3
+ name: model-selection
4
+ description: Tier vs model selection for schedule rows + `GET /api/schedule/options` discovery endpoint. Mutual-exclusion rules, legacy alias rewrite, composite-form disambiguator.
5
+ ---
6
+
7
+ # Tier / Model selection
8
+
9
+ `tier` is the backend-neutral cost knob — **prefer it**. `model` pins
10
+ a specific registered model id when the row must run against a
11
+ particular backend (e.g. a routine that depends on Opus reasoning
12
+ even after `/settings/models` re-routes the process key). The two are
13
+ **mutually exclusive on a single row** — passing both returns
14
+ `schedule.tier_and_model_conflict` (no "tier wins" precedence).
15
+
16
+ | `tier` | Class | When |
17
+ |---|---|---|
18
+ | `"lite"` | Haiku | hourly polling / health checks (e.g. docker `ps` summary) |
19
+ | `"medium"` | Sonnet | lock against future `/settings/models` re-routes |
20
+ | `"high"` | Opus | one-off generative work driving user-visible output |
21
+
22
+ `model` accepts:
23
+
24
+ - **Legacy aliases** — `"sonnet"` / `"opus"`. Auto-rewritten at the
25
+ route to `tier:"medium"` / `tier:"high"`; the alias is not stored
26
+ verbatim.
27
+ - **Registered model ids** — any id from `MODEL_REGISTRY` across the
28
+ four backends. Examples: `claude-opus-4-7`, `claude-sonnet-4-6`,
29
+ `claude-haiku-4-5-20251001`, `gpt-5.4`, `gemini-3.1-pro-preview`.
30
+ The row persists `(model, backend_id)` together so the dispatcher
31
+ honors the pin at fire time.
32
+ - **Composite `<backendId>/<modelId>`** — disambiguator for a future
33
+ registry that has the same model id under multiple backends (today
34
+ unreachable but accepted). The prefix MUST be one of `claude` /
35
+ `codex` / `gemini` / `opencode`; opencode model ids like
36
+ `anthropic/claude-opus-4-7` are NOT composites and fall through to
37
+ the cross-backend scan.
38
+
39
+ Unknown / ambiguous / deprecated model tokens surface through the
40
+ error envelope's `validValues` field — read it instead of guessing.
41
+ The full code list lives in `references/errors.md`.
42
+
43
+ ## PATCH semantics — tier ↔ model swap
44
+
45
+ A row carries at most one pin at rest. On PATCH:
46
+
47
+ - Pass `null` to clear one and a concrete value to set the other in
48
+ the same request — that is the documented form for swapping a
49
+ tier-pinned row to a model-pinned row (and vice versa).
50
+ - Setting a registered `model` token also clears any prior
51
+ `tier_override` automatically; setting `tier` does not auto-clear
52
+ `model` — pair the change with `"model": null` when the intent is
53
+ to swap.
54
+ - Setting a legacy alias (`sonnet` / `opus`) on PATCH is rewritten to
55
+ `tier:"medium"` / `tier:"high"`; the alias is never stored verbatim.
56
+
57
+ ## Discovery — `GET /api/schedule/options`
58
+
59
+ Read-only one-stop endpoint that returns every value the daemon will
60
+ accept right now: registered models per backend, model aliases,
61
+ allowed tiers, recurrence frequencies, `daysOfWeek` map, hourly /
62
+ monthly bounds (`intervalHours` 1..23, `minuteOfHour` 0..59,
63
+ `onMissingDay` default), and the operator's configured timezone.
64
+ Fetch once per cold session before composing tricky schedules; the
65
+ error envelope also cites this endpoint via `docsUrl` so you can
66
+ recover after a 400.
67
+
68
+ ```bash
69
+ curl -s http://localhost:8321/api/schedule/options
70
+ ```
71
+
72
+ Response shape:
73
+
74
+ ```jsonc
75
+ {
76
+ "tiers": ["lite", "medium", "high"],
77
+ "modelAliases": { "sonnet": "medium", "opus": "high" },
78
+ "models": {
79
+ "claude": [{ "id": "claude-opus-4-7", "tier": "high", "deprecated": false }, ...],
80
+ "codex": [...],
81
+ "gemini": [...],
82
+ "opencode": [...]
83
+ },
84
+ "frequencies": ["hourly", "daily", "weekly", "monthly"],
85
+ "daysOfWeek": { "0": "Sun", "1": "Mon", ..., "6": "Sat" },
86
+ "recurrence": {
87
+ "intervalHours": { "min": 1, "max": 23 },
88
+ "minuteOfHour": { "min": 0, "max": 59 },
89
+ "daysOfMonth": { "min": 1, "max": 31 },
90
+ "onMissingDay": { "values": ["skip", "lastDayOfMonth"], "default": "lastDayOfMonth" }
91
+ },
92
+ "timeFormat": "HH:MM (24h)",
93
+ "timezoneExample": "Asia/Tokyo",
94
+ "defaults": { "timezone": "<operator's configured primary timezone>" }
95
+ }
96
+ ```