@aitne/daemon 0.1.9 → 0.1.11

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 (333) hide show
  1. package/dist/adapters/adapter-watchdog.d.ts +70 -0
  2. package/dist/adapters/adapter-watchdog.js +115 -0
  3. package/dist/adapters/discord.d.ts +17 -1
  4. package/dist/adapters/discord.js +33 -0
  5. package/dist/adapters/notification-manager.d.ts +27 -1
  6. package/dist/adapters/notification-manager.js +54 -39
  7. package/dist/adapters/slack-adapter.d.ts +26 -1
  8. package/dist/adapters/slack-adapter.js +41 -0
  9. package/dist/adapters/telegram-adapter.d.ts +18 -1
  10. package/dist/adapters/telegram-adapter.js +41 -2
  11. package/dist/adapters/types.d.ts +20 -0
  12. package/dist/adapters/whatsapp-adapter.d.ts +26 -7
  13. package/dist/adapters/whatsapp-adapter.js +74 -21
  14. package/dist/api/env-writer.d.ts +1 -0
  15. package/dist/api/env-writer.js +17 -7
  16. package/dist/api/helpers/agent-errors-registry.d.ts +5 -5
  17. package/dist/api/helpers/agent-errors-registry.js +5 -5
  18. package/dist/api/routes/agent-schedule.js +5 -1
  19. package/dist/api/routes/agent.js +33 -12
  20. package/dist/api/routes/agents/index.js +75 -16
  21. package/dist/api/routes/agents/views.d.ts +37 -2
  22. package/dist/api/routes/agents/views.js +64 -2
  23. package/dist/api/routes/apple-calendar.js +4 -1
  24. package/dist/api/routes/background-task.d.ts +22 -0
  25. package/dist/api/routes/background-task.js +338 -0
  26. package/dist/api/routes/browser-history.js +9 -1
  27. package/dist/api/routes/calendar.js +12 -2
  28. package/dist/api/routes/context/path-resolve.js +6 -1
  29. package/dist/api/routes/context/permissions.js +12 -2
  30. package/dist/api/routes/context/snapshots.js +0 -3
  31. package/dist/api/routes/context/write.js +3 -17
  32. package/dist/api/routes/dashboard/config.js +58 -12
  33. package/dist/api/routes/dashboard/cost-approvals.js +66 -0
  34. package/dist/api/routes/dashboard/notifications.js +9 -9
  35. package/dist/api/routes/dashboard/oauth-google.js +5 -3
  36. package/dist/api/routes/feedback.d.ts +3 -0
  37. package/dist/api/routes/feedback.js +349 -0
  38. package/dist/api/routes/git.js +10 -3
  39. package/dist/api/routes/github.js +5 -1
  40. package/dist/api/routes/integrations/crud-patch.js +5 -1
  41. package/dist/api/routes/integrations-reconcile.js +2 -2
  42. package/dist/api/routes/mcp.js +65 -13
  43. package/dist/api/routes/notion.d.ts +1 -1
  44. package/dist/api/routes/observations.js +7 -7
  45. package/dist/api/routes/obsidian.d.ts +1 -1
  46. package/dist/api/routes/receipts.js +5 -1
  47. package/dist/api/routes/setup-migrate.js +1 -1
  48. package/dist/api/routes/setup.js +1 -1
  49. package/dist/api/routes/task-flows.d.ts +1 -1
  50. package/dist/api/routes/task-flows.js +1 -1
  51. package/dist/api/routes/tuning.d.ts +29 -0
  52. package/dist/api/routes/tuning.js +304 -0
  53. package/dist/api/server.d.ts +44 -16
  54. package/dist/api/server.js +12 -0
  55. package/dist/bootstrap/adapters.d.ts +19 -0
  56. package/dist/bootstrap/adapters.js +61 -0
  57. package/dist/bootstrap/api.d.ts +5 -3
  58. package/dist/bootstrap/api.js +45 -13
  59. package/dist/bootstrap/catchup.d.ts +1 -1
  60. package/dist/bootstrap/catchup.js +11 -11
  61. package/dist/bootstrap/event-pipeline.d.ts +11 -0
  62. package/dist/bootstrap/event-pipeline.js +246 -8
  63. package/dist/bootstrap/observers.js +9 -6
  64. package/dist/bootstrap/schedule-helpers.d.ts +104 -6
  65. package/dist/bootstrap/schedule-helpers.js +172 -19
  66. package/dist/config.js +32 -12
  67. package/dist/core/agent-core.d.ts +33 -1
  68. package/dist/core/agent-core.js +36 -1
  69. package/dist/core/agents/activity-scan-cadence.d.ts +103 -0
  70. package/dist/core/agents/activity-scan-cadence.js +127 -0
  71. package/dist/core/agents/agent-route-override.d.ts +53 -0
  72. package/dist/core/agents/agent-route-override.js +69 -0
  73. package/dist/core/agents/builtin-registry.d.ts +51 -14
  74. package/dist/core/agents/builtin-registry.js +92 -15
  75. package/dist/core/agents/config-gate-reconcile.d.ts +38 -0
  76. package/dist/core/agents/config-gate-reconcile.js +51 -0
  77. package/dist/core/agents/cron-substitute.d.ts +1 -1
  78. package/dist/core/agents/cron-substitute.js +1 -1
  79. package/dist/core/agents/custom-routine-migration.d.ts +60 -0
  80. package/dist/core/agents/custom-routine-migration.js +149 -0
  81. package/dist/core/agents/firing-blocked.d.ts +1 -1
  82. package/dist/core/agents/hourly-cadence.d.ts +102 -0
  83. package/dist/core/agents/hourly-cadence.js +126 -0
  84. package/dist/core/agents/loader-boot.js +23 -0
  85. package/dist/core/agents/loader.d.ts +19 -0
  86. package/dist/core/agents/loader.js +34 -2
  87. package/dist/core/agents/override-merge.d.ts +1 -1
  88. package/dist/core/agents/override-merge.js +9 -1
  89. package/dist/core/agents/recurrence-convert.d.ts +1 -1
  90. package/dist/core/agents/recurrence-convert.js +1 -1
  91. package/dist/core/agents/recurring-schedule-adapter.js +8 -0
  92. package/dist/core/alerts.js +6 -6
  93. package/dist/core/backends/auth-health-monitor.d.ts +2 -2
  94. package/dist/core/backends/auth-health-monitor.js +1 -1
  95. package/dist/core/backends/backend-router.d.ts +27 -1
  96. package/dist/core/backends/backend-router.js +165 -1
  97. package/dist/core/backends/claude-code-core.d.ts +71 -31
  98. package/dist/core/backends/claude-code-core.js +282 -54
  99. package/dist/core/backends/cli-quota-guards.d.ts +29 -1
  100. package/dist/core/backends/cli-quota-guards.js +40 -5
  101. package/dist/core/backends/codex-core.d.ts +6 -0
  102. package/dist/core/backends/codex-core.js +22 -6
  103. package/dist/core/backends/failure-spend.d.ts +58 -0
  104. package/dist/core/backends/failure-spend.js +137 -0
  105. package/dist/core/backends/gemini-cli-core.d.ts +6 -0
  106. package/dist/core/backends/gemini-cli-core.js +38 -6
  107. package/dist/core/backends/model-registry.d.ts +1 -1
  108. package/dist/core/backends/model-registry.js +4 -4
  109. package/dist/core/backends/opencode-core.d.ts +1 -1
  110. package/dist/core/backends/opencode-core.js +5 -5
  111. package/dist/core/backends/plan-presets.js +47 -18
  112. package/dist/core/bang-commands/commands-cost.js +3 -1
  113. package/dist/core/bang-commands/commands-report.js +4 -3
  114. package/dist/core/bang-commands/commands-research.js +4 -1
  115. package/dist/core/bang-commands/commands-revert-tuning.d.ts +18 -0
  116. package/dist/core/bang-commands/commands-revert-tuning.js +63 -0
  117. package/dist/core/bang-commands/commands-stop-start.js +3 -3
  118. package/dist/core/bang-commands/commands-task-control.d.ts +19 -0
  119. package/dist/core/bang-commands/commands-task-control.js +147 -0
  120. package/dist/core/bang-commands/commands-wiki.js +5 -5
  121. package/dist/core/bang-commands/index.d.ts +2 -0
  122. package/dist/core/bang-commands/index.js +12 -0
  123. package/dist/core/bang-commands/registry.d.ts +12 -0
  124. package/dist/core/browser-history/research-cluster-fanout.d.ts +28 -14
  125. package/dist/core/browser-history/research-cluster-fanout.js +39 -16
  126. package/dist/core/channel-timeline.d.ts +5 -1
  127. package/dist/core/channel-timeline.js +13 -0
  128. package/dist/core/context/index-reconciler.js +5 -2
  129. package/dist/core/context/policy-index-reconciler.d.ts +6 -4
  130. package/dist/core/context/policy-index-runner.js +25 -6
  131. package/dist/core/context-builder-calendar.js +10 -2
  132. package/dist/core/context-builder-conversation.d.ts +8 -1
  133. package/dist/core/context-builder-conversation.js +41 -7
  134. package/dist/core/context-builder-yesterday.js +4 -3
  135. package/dist/core/context-builder.d.ts +7 -2
  136. package/dist/core/context-builder.js +193 -5
  137. package/dist/core/context-file-serializer.d.ts +1 -1
  138. package/dist/core/context-file-serializer.js +1 -1
  139. package/dist/core/context-health.js +2 -2
  140. package/dist/core/context-paths.d.ts +11 -1
  141. package/dist/core/context-paths.js +17 -1
  142. package/dist/core/context-validation/prepare-write.js +1 -1
  143. package/dist/core/context-validation/routine-rulebook.d.ts +1 -1
  144. package/dist/core/context-vault-aliases.d.ts +0 -13
  145. package/dist/core/context-vault-aliases.js +37 -0
  146. package/dist/core/custom-routines.d.ts +99 -0
  147. package/dist/core/custom-routines.js +187 -0
  148. package/dist/core/daemon-api-cli.js +50 -1
  149. package/dist/core/day-boundary.d.ts +46 -0
  150. package/dist/core/day-boundary.js +40 -0
  151. package/dist/core/dispatcher-activity-scan.d.ts +221 -0
  152. package/dist/core/dispatcher-activity-scan.js +775 -0
  153. package/dist/core/dispatcher-error-handling.d.ts +6 -11
  154. package/dist/core/dispatcher-error-handling.js +38 -62
  155. package/dist/core/dispatcher-hourly-check.js +6 -1
  156. package/dist/core/dispatcher-message-handler.d.ts +10 -0
  157. package/dist/core/dispatcher-message-handler.js +24 -0
  158. package/dist/core/dispatcher-morning-routine.d.ts +6 -6
  159. package/dist/core/dispatcher-morning-routine.js +13 -13
  160. package/dist/core/dispatcher-result-processor.d.ts +33 -0
  161. package/dist/core/dispatcher-result-processor.js +167 -11
  162. package/dist/core/dispatcher-scheduled-background-task.d.ts +42 -0
  163. package/dist/core/dispatcher-scheduled-background-task.js +89 -0
  164. package/dist/core/dispatcher-scheduled-tasks.d.ts +104 -1
  165. package/dist/core/dispatcher-scheduled-tasks.js +480 -8
  166. package/dist/core/dispatcher-task-delivery.d.ts +105 -0
  167. package/dist/core/dispatcher-task-delivery.js +555 -0
  168. package/dist/core/dispatcher-types.d.ts +48 -9
  169. package/dist/core/dispatcher-types.js +3 -3
  170. package/dist/core/dispatcher.d.ts +112 -31
  171. package/dist/core/dispatcher.js +297 -60
  172. package/dist/core/dm-freshness-metrics.d.ts +1 -1
  173. package/dist/core/drift-effects.js +2 -2
  174. package/dist/core/feedback/consolidation-prep.d.ts +94 -0
  175. package/dist/core/feedback/consolidation-prep.js +254 -0
  176. package/dist/core/feedback/eviction-scorer.d.ts +81 -0
  177. package/dist/core/feedback/eviction-scorer.js +136 -0
  178. package/dist/core/feedback/lesson-format.d.ts +79 -0
  179. package/dist/core/feedback/lesson-format.js +199 -0
  180. package/dist/core/feedback/lesson-injection.d.ts +98 -0
  181. package/dist/core/feedback/lesson-injection.js +174 -0
  182. package/dist/core/feedback/lesson-merge.d.ts +51 -0
  183. package/dist/core/feedback/lesson-merge.js +88 -0
  184. package/dist/core/feedback/lesson-store-overview.d.ts +46 -0
  185. package/dist/core/feedback/lesson-store-overview.js +42 -0
  186. package/dist/core/feedback/promotion-gate.d.ts +69 -0
  187. package/dist/core/feedback/promotion-gate.js +117 -0
  188. package/dist/core/feedback/regeneralization-prep.d.ts +87 -0
  189. package/dist/core/feedback/regeneralization-prep.js +152 -0
  190. package/dist/core/feedback/scope-parser.d.ts +86 -0
  191. package/dist/core/feedback/scope-parser.js +141 -0
  192. package/dist/core/feedback/self-performance-prep.d.ts +186 -0
  193. package/dist/core/feedback/self-performance-prep.js +541 -0
  194. package/dist/core/feedback/tuning-actuator.d.ts +198 -0
  195. package/dist/core/feedback/tuning-actuator.js +432 -0
  196. package/dist/core/feedback/tuning-recommender.d.ts +247 -0
  197. package/dist/core/feedback/tuning-recommender.js +580 -0
  198. package/dist/core/feedback/tuning-revert-monitor.d.ts +90 -0
  199. package/dist/core/feedback/tuning-revert-monitor.js +213 -0
  200. package/dist/core/health-monitor.d.ts +6 -0
  201. package/dist/core/health-monitor.js +1 -1
  202. package/dist/core/injection-policy.d.ts +83 -1
  203. package/dist/core/injection-policy.js +61 -3
  204. package/dist/core/integration-main-backend.js +4 -0
  205. package/dist/core/management-md.d.ts +2 -2
  206. package/dist/core/management-md.js +51 -13
  207. package/dist/core/morning/orchestrator.d.ts +2 -2
  208. package/dist/core/morning/orchestrator.js +2 -2
  209. package/dist/core/notification-gate.d.ts +64 -0
  210. package/dist/core/notification-gate.js +51 -0
  211. package/dist/core/notification-rate-limit.d.ts +40 -0
  212. package/dist/core/notification-rate-limit.js +50 -0
  213. package/dist/core/policy-files.d.ts +1 -1
  214. package/dist/core/policy-files.js +2 -2
  215. package/dist/core/pre-pass-freshness.d.ts +4 -4
  216. package/dist/core/retention.d.ts +5 -0
  217. package/dist/core/retention.js +20 -4
  218. package/dist/core/review-context.d.ts +1 -1
  219. package/dist/core/review-context.js +10 -5
  220. package/dist/core/roadmap-write-lock.d.ts +2 -1
  221. package/dist/core/roadmap-write-lock.js +15 -10
  222. package/dist/core/routine-acquisition-plan.d.ts +47 -1
  223. package/dist/core/routine-acquisition-plan.js +78 -20
  224. package/dist/core/routine-fetch-window-retry.js +7 -4
  225. package/dist/core/routine-fetch-window-runner.d.ts +39 -3
  226. package/dist/core/routine-fetch-window-runner.js +264 -13
  227. package/dist/core/routine-windows.d.ts +2 -2
  228. package/dist/core/routine-windows.js +8 -5
  229. package/dist/core/scheduler.d.ts +175 -16
  230. package/dist/core/scheduler.js +559 -102
  231. package/dist/core/signal-detector.d.ts +51 -1
  232. package/dist/core/signal-detector.js +321 -24
  233. package/dist/core/skills-compiler-denied-tools.js +2 -2
  234. package/dist/core/skills-compiler-skill-index.d.ts +2 -2
  235. package/dist/core/skills-compiler-skill-index.js +2 -2
  236. package/dist/core/skills-compiler-variants.d.ts +1 -1
  237. package/dist/core/skills-compiler-variants.js +8 -0
  238. package/dist/core/skills-compiler.d.ts +29 -26
  239. package/dist/core/skills-compiler.js +117 -81
  240. package/dist/core/skills-manifest.d.ts +37 -0
  241. package/dist/core/skills-manifest.js +73 -2
  242. package/dist/core/sleep-inhibitor.d.ts +79 -0
  243. package/dist/core/sleep-inhibitor.js +132 -0
  244. package/dist/core/slim-system-prompt-loader.d.ts +77 -0
  245. package/dist/core/slim-system-prompt-loader.js +141 -0
  246. package/dist/core/spawn-gates.d.ts +126 -0
  247. package/dist/core/spawn-gates.js +180 -0
  248. package/dist/core/today-direct-writer.d.ts +60 -14
  249. package/dist/core/today-direct-writer.js +90 -13
  250. package/dist/core/today-write-lock.d.ts +4 -2
  251. package/dist/core/today-write-lock.js +30 -20
  252. package/dist/core/wake-detector.d.ts +55 -0
  253. package/dist/core/wake-detector.js +80 -0
  254. package/dist/core/wiki/compile-lock.d.ts +1 -1
  255. package/dist/core/wiki/compile-lock.js +1 -1
  256. package/dist/core/wiki/wiki-fts.js +13 -6
  257. package/dist/core/workdir.js +15 -6
  258. package/dist/db/activity-scan-signals.d.ts +77 -0
  259. package/dist/db/activity-scan-signals.js +378 -0
  260. package/dist/db/agents-store.d.ts +28 -0
  261. package/dist/db/agents-store.js +62 -0
  262. package/dist/db/background-task-clarifications-store.d.ts +81 -0
  263. package/dist/db/background-task-clarifications-store.js +152 -0
  264. package/dist/db/background-task-store.d.ts +207 -0
  265. package/dist/db/background-task-store.js +380 -0
  266. package/dist/db/browser-history-store.d.ts +39 -6
  267. package/dist/db/browser-history-store.js +51 -7
  268. package/dist/db/browser-task-clarifications-store.d.ts +12 -0
  269. package/dist/db/browser-task-clarifications-store.js +35 -5
  270. package/dist/db/browser-task-store.d.ts +3 -0
  271. package/dist/db/browser-task-store.js +29 -4
  272. package/dist/db/deferred-dm.d.ts +86 -0
  273. package/dist/db/deferred-dm.js +199 -0
  274. package/dist/db/feedback-signals-store.d.ts +77 -0
  275. package/dist/db/feedback-signals-store.js +144 -0
  276. package/dist/db/migrations.js +380 -0
  277. package/dist/db/observations.d.ts +2 -2
  278. package/dist/db/observations.js +3 -3
  279. package/dist/db/schema.js +260 -22
  280. package/dist/db/voice-transcripts-store.d.ts +1 -1
  281. package/dist/index.js +86 -29
  282. package/dist/messaging/browser-task-mcp-notifier.d.ts +12 -70
  283. package/dist/messaging/browser-task-mcp-notifier.js +30 -151
  284. package/dist/messaging/browser-task-screenshot-attachment.d.ts +15 -0
  285. package/dist/messaging/browser-task-screenshot-attachment.js +63 -0
  286. package/dist/observers/delegated-sync-worker.d.ts +6 -6
  287. package/dist/observers/delegated-sync-worker.js +10 -10
  288. package/dist/observers/git-delegated-cron.d.ts +1 -1
  289. package/dist/observers/git-delegated-cron.js +2 -2
  290. package/dist/observers/github-poller-classifier.d.ts +3 -3
  291. package/dist/observers/github-poller-classifier.js +3 -3
  292. package/dist/observers/imminent-event-scheduler.d.ts +1 -1
  293. package/dist/observers/imminent-event-scheduler.js +1 -1
  294. package/dist/observers/mail-poller.d.ts +1 -0
  295. package/dist/observers/mail-poller.js +42 -3
  296. package/dist/observers/observation-summarizer/summarizer-client.d.ts +2 -2
  297. package/dist/observers/observation-summarizer/summarizer-client.js +2 -2
  298. package/dist/observers/observation-summarizer/worker.d.ts +2 -2
  299. package/dist/observers/observation-summarizer/worker.js +4 -4
  300. package/dist/observers/obsidian-watcher.d.ts +1 -1
  301. package/dist/observers/obsidian-watcher.js +1 -1
  302. package/dist/safety/agent-write-tracker.d.ts +4 -4
  303. package/dist/safety/agent-write-tracker.js +4 -4
  304. package/dist/safety/always-disallowed.d.ts +1 -1
  305. package/dist/safety/always-disallowed.js +39 -0
  306. package/dist/safety/audit.d.ts +43 -5
  307. package/dist/safety/audit.js +86 -18
  308. package/dist/safety/risk-classifier.d.ts +6 -0
  309. package/dist/safety/risk-classifier.js +97 -18
  310. package/dist/scheduler/activity-scan-gate.d.ts +86 -0
  311. package/dist/scheduler/activity-scan-gate.js +132 -0
  312. package/dist/services/background-task/background-task-budget.d.ts +80 -0
  313. package/dist/services/background-task/background-task-budget.js +91 -0
  314. package/dist/services/background-task/background-task-driver.d.ts +105 -0
  315. package/dist/services/background-task/background-task-driver.js +416 -0
  316. package/dist/services/background-task/background-task-runner.d.ts +96 -0
  317. package/dist/services/background-task/background-task-runner.js +673 -0
  318. package/dist/services/background-task/background-task-tools.d.ts +84 -0
  319. package/dist/services/background-task/background-task-tools.js +247 -0
  320. package/dist/services/background-task/background-task-transition-events.d.ts +43 -0
  321. package/dist/services/background-task/background-task-transition-events.js +54 -0
  322. package/dist/services/browser-history/automation/egress-denylist.d.ts +1 -1
  323. package/dist/services/browser-history/automation/egress-denylist.js +34 -8
  324. package/dist/services/browser-history/lifecycle/platform.js +44 -2
  325. package/dist/services/browser-history/managed-chromium/sandbox-launcher.js +0 -1
  326. package/dist/services/browser-task/browser-task-runner.js +53 -8
  327. package/dist/services/mcp/probe.js +30 -8
  328. package/dist/services/observations-batch.d.ts +1 -1
  329. package/dist/services/observations-batch.js +2 -2
  330. package/dist/settings/runtime-settings.d.ts +45 -12
  331. package/dist/settings/runtime-settings.js +215 -40
  332. package/dist/settings/settings-store.js +11 -3
  333. package/package.json +4 -4
@@ -44,11 +44,12 @@
44
44
  * does not own the manager's lifecycle.
45
45
  */
46
46
  import { EventPriority, createEvent, formatSqliteDatetime, getAgentDayDateStr, isBackendId, isKnowledgeImportEvent, isRoutineEvent, resolveProcessKey, } from "@aitne/shared";
47
- import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync, } from "node:fs";
47
+ import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, writeFileSync, } from "node:fs";
48
48
  import { join } from "node:path";
49
49
  import { randomUUID } from "node:crypto";
50
- import { CONTEXT_RELATIVE_PATHS } from "./context-paths.js";
50
+ import { CONTEXT_RELATIVE_PATHS, agentLessonsPath } from "./context-paths.js";
51
51
  import { getContextDir } from "../config.js";
52
+ import { ensureTodaySkeleton } from "./today-direct-writer.js";
52
53
  import { cleanupSessionWorkdir, ensureBackendMaterialized, } from "./workdir.js";
53
54
  import { readIntegrations, readIntegrationState } from "../db/integrations-store.js";
54
55
  import { refreshInterestsReflection } from "../services/browser-history/refresh-interests-reflection.js";
@@ -60,6 +61,13 @@ import { getRepository, getRepositoryByLocalPath, recordManagementInitDone, reco
60
61
  import { runRepositoryManagementInit, runRepositoryManagementScan, } from "./repository-management-docs.js";
61
62
  import { routineWindowKeyFromEvent } from "./routine-fetch-window-runner.js";
62
63
  import { routineHasWindows } from "./routine-windows.js";
64
+ import { buildFeedbackWorksheet, gatherFeedbackWorksheetScopes, lessonCapsForScope, GLOBAL_LESSON_ENTRY_CAP, PER_AGENT_LESSON_ENTRY_CAP, } from "./feedback/consolidation-prep.js";
65
+ import { buildSelfPerformanceBlock, gatherSelfPerformanceData, summarizeLessonStoreUtilization, } from "./feedback/self-performance-prep.js";
66
+ import { TUNING_PENDING_CYCLE_STATE_KEY, buildTuningRecommendations, createPendingTuningCycle, gatherFailingRecurringSchedules, renderTuningRecommendationsBlock, } from "./feedback/tuning-recommender.js";
67
+ import { readRuntimeState, writeRuntimeState } from "../db/runtime-state.js";
68
+ import { buildRegeneralizationWorksheet, } from "./feedback/regeneralization-prep.js";
69
+ import { isSafeAgentSlug, scopeStoreFile, } from "./feedback/scope-parser.js";
70
+ import { feedbackRetentionCutoff, sweepConsumedFeedbackSignals, } from "../db/feedback-signals-store.js";
63
71
  import { morningRoutineRanToday } from "../bootstrap/schedule-helpers.js";
64
72
  import { resolveAgentId } from "./agents/agent-id-resolver.js";
65
73
  import { loadEffectiveDefinition } from "./agents/effective-definition.js";
@@ -198,6 +206,33 @@ export const REFRESH_ARCHITECTURE_ALLOWED_TOOLS = [
198
206
  "Bash(curl http://localhost:8321/api/repositories/*/architecture-section*)",
199
207
  "Bash(jq *)",
200
208
  ];
209
+ /**
210
+ * BACKGROUND_TASK_RUNNER_DESIGN.md §4.5 / §4.5-bis / Phase 4 — the active
211
+ * delivery turn is a NO-TOOL DM turn. Its sole job is to weave the
212
+ * already-injected artifact (`taskContext.task_delivery.report` carries the
213
+ * full verbatim result) into the live conversation; the agent's reply text
214
+ * IS the DM (recorded by the result processor) and any deliverable files
215
+ * are attached by the daemon, not by the agent. So the turn needs no tools
216
+ * at all — and an EMPTY override structurally prevents it from taking
217
+ * "further action" (spawning another task, writing memory, sending mail)
218
+ * during what should be a pure phrasing turn. Follow-up turns ("what did it
219
+ * find?") are ordinary DM turns and keep the full envelope, including the
220
+ * `GET /api/background-task/:id` read affordance.
221
+ */
222
+ export const TASK_DELIVERY_TURN_ALLOWED_TOOLS = [];
223
+ /**
224
+ * True when `taskCtx` is a synthetic `scheduled.dm` event minted by the
225
+ * task-delivery handler (`createScheduledDmDeliveryEvent`) — the only place
226
+ * that sets the `task_delivery` block. Used to pin the no-tool clamp above.
227
+ * Keying off this structural marker (not `event.source`) is fail-safe: the
228
+ * clamp only ever NARROWS the envelope, so a false positive degrades a turn
229
+ * to no-tool rather than widening anything.
230
+ */
231
+ export function isTaskDeliveryTurn(taskCtx) {
232
+ return (!!taskCtx
233
+ && typeof taskCtx === "object"
234
+ && "task_delivery" in taskCtx);
235
+ }
201
236
  /**
202
237
  * Backends that honor the per-execute `allowedToolsOverride` clamp end-to-
203
238
  * end. Claude consumes the list verbatim through the SDK's `dontAsk` +
@@ -209,6 +244,14 @@ export const REFRESH_ARCHITECTURE_ALLOWED_TOOLS = [
209
244
  * envelope; the operator sees an `agent_actions` row of action_type
210
245
  * `scheduled_task_clamp_unsupported` and a clear log line.
211
246
  *
247
+ * Exception — the Phase-4 task-delivery turn (`isTaskDeliveryTurn`)
248
+ * consumes this same set but DEGRADES rather than refuses: when the
249
+ * resolved backend can't enforce `[]` it still runs the delivery turn
250
+ * with the default envelope (logged), because failing to deliver the
251
+ * owner's result is worse than running the phrasing turn untooled. The
252
+ * clamp is keyed off `binding.main.backendId`, so — as with the refuse
253
+ * path — a runtime fallback to a non-claude backend is not re-evaluated.
254
+ *
212
255
  * Add a backend here only after verifying its core threads
213
256
  * `allowedToolsOverride` through to its concrete deny enforcement layer
214
257
  * — NOT just into the CLI flag set.
@@ -227,6 +270,7 @@ export class ScheduledTaskRunner {
227
270
  morningRoutine;
228
271
  fetchWindowRunner;
229
272
  roadmapWriteLock;
273
+ todayWriteLock;
230
274
  writeTracker;
231
275
  getConfiguredServices;
232
276
  getActiveMailAccounts;
@@ -243,6 +287,7 @@ export class ScheduledTaskRunner {
243
287
  this.morningRoutine = deps.morningRoutine;
244
288
  this.fetchWindowRunner = deps.fetchWindowRunner;
245
289
  this.roadmapWriteLock = deps.roadmapWriteLock;
290
+ this.todayWriteLock = deps.todayWriteLock;
246
291
  this.writeTracker = deps.writeTracker;
247
292
  this.getConfiguredServices = deps.getConfiguredServices;
248
293
  this.getActiveMailAccounts = deps.getActiveMailAccounts;
@@ -454,6 +499,23 @@ export class ScheduledTaskRunner {
454
499
  this.markScheduledTaskCompleted(event);
455
500
  return;
456
501
  }
502
+ // BACKGROUND_TASK_RUNNER_DESIGN.md §4.5 / Phase 4 — pin the no-tool
503
+ // clamp on the active delivery turn. Mutually exclusive with the
504
+ // refresh-architecture clamp (a delivery turn is never that process
505
+ // key). Unlike the safety-critical refresh/curation clamps, this is a
506
+ // HARDENING: when the resolved backend can't enforce a per-execute
507
+ // clamp we DEGRADE (deliver with the default envelope) rather than
508
+ // refuse — failing to deliver the owner's result would be the worse
509
+ // outcome. The degrade is logged per the "no silent drops" posture.
510
+ const taskDeliveryToolClamp = isTaskDeliveryTurn(taskCtx)
511
+ ? TOOL_CLAMP_SUPPORTING_BACKENDS.has(binding.main.backendId)
512
+ ? TASK_DELIVERY_TURN_ALLOWED_TOOLS
513
+ : undefined
514
+ : undefined;
515
+ if (isTaskDeliveryTurn(taskCtx) && taskDeliveryToolClamp === undefined) {
516
+ logger.info({ backendId: binding.main.backendId, correlationId: event.correlationId }, "task.delivery active turn: resolved backend does not honor the per-execute tool clamp; delivering with the default DM envelope (degraded, not refused)");
517
+ }
518
+ const effectiveAllowedToolsOverride = refreshArchitectureOverride ?? taskDeliveryToolClamp;
457
519
  // AGENT_DEFINITIONS_DESIGN.md §4.2 — fold the firing Agent's
458
520
  // `tools.skills` onto the process-key skill bundle. `undefined` for every
459
521
  // non-Agent firing (managed task, git project doc, automation trigger) and
@@ -467,8 +529,8 @@ export class ScheduledTaskRunner {
467
529
  requestedTier,
468
530
  preResolvedBinding: binding,
469
531
  reassemblePrompt,
470
- ...(refreshArchitectureOverride
471
- ? { allowedToolsOverride: refreshArchitectureOverride }
532
+ ...(effectiveAllowedToolsOverride
533
+ ? { allowedToolsOverride: effectiveAllowedToolsOverride }
472
534
  : {}),
473
535
  ...(agentSkillOverride
474
536
  ? {
@@ -801,7 +863,7 @@ export class ScheduledTaskRunner {
801
863
  // row insert (today.md is fresh, audit row is missing);
802
864
  // - the user manually edits today.md to the current date (CLAUDE.md
803
865
  // calls this out as a documented edit path).
804
- // In both, the hourly_check pre-routine gate (`morningRoutineRanToday`)
866
+ // In both, the activity_scan pre-routine gate (`morningRoutineRanToday`)
805
867
  // would keep refusing to run because the audit row is absent, the
806
868
  // pre-routine gate kept enqueuing wake rows, and each wake row was
807
869
  // fast-path completed here — never producing the audit row that would
@@ -837,6 +899,15 @@ export class ScheduledTaskRunner {
837
899
  // carries the previous attempt so executeMorningRoutine → scheduleMorningRetry
838
900
  // can increment properly. correlationId tracks back to the original
839
901
  // cron morning_routine for log correlation.
902
+ //
903
+ // `agentId` is carried over from the wake event (stamped there by
904
+ // `Dispatcher.beginAgentExecution` via `task_context.agent_id` /
905
+ // `task_context.routine`): the cron path's RoutineEvent gets the stamp
906
+ // directly, so dropping it here would make run-now / retry firings
907
+ // ignore the morning-routine Agent's overrides + lesson injection while
908
+ // cron firings honour them — the same manual-vs-cron divergence class
909
+ // AGENT_DEFINITIONS_KNOWN_LIMITATIONS §1 fixed for user Agents.
910
+ const wakeAgentId = event.data?.agentId;
840
911
  const synthEvent = {
841
912
  ...createEvent({
842
913
  type: "routine.morning_routine",
@@ -848,12 +919,15 @@ export class ScheduledTaskRunner {
848
919
  priority: retryCount > 0 ? EventPriority.NORMAL : EventPriority.HIGH,
849
920
  correlationId: taskCtx.originalCorrelationId ?? event.correlationId,
850
921
  data: {
922
+ ...(typeof wakeAgentId === "string" && wakeAgentId.length > 0
923
+ ? { agentId: wakeAgentId }
924
+ : {}),
851
925
  ...(retryCount > 0 ? { retryCount, isRetry: true } : {}),
852
926
  ...(Array.isArray(taskCtx.postCatchupRoutines)
853
927
  ? { postCatchupRoutines: taskCtx.postCatchupRoutines }
854
928
  : {}),
855
- ...(taskCtx.postCatchupHourlyCheck === true
856
- ? { postCatchupHourlyCheck: true }
929
+ ...(taskCtx.postCatchupActivityScan === true
930
+ ? { postCatchupActivityScan: true }
857
931
  : {}),
858
932
  ...(typeof taskCtx.source === "string"
859
933
  ? { queuedSource: taskCtx.source }
@@ -1061,7 +1135,7 @@ export class ScheduledTaskRunner {
1061
1135
  try {
1062
1136
  // docs/design/appendices/routine-data-acquisition.md Phase 4 / D4 — pre-pass for
1063
1137
  // routine events whose ProcessKey appears in `ROUTINE_WINDOWS`
1064
- // (today_refresh, evening_review, weekly_review). The hourly_check
1138
+ // (today_refresh, evening_review, weekly_review). The activity_scan
1065
1139
  // and morning_routine dispatch paths attach their own
1066
1140
  // `fetchReportBlock` upstream (D2 / D3); we honour an existing
1067
1141
  // attachment to avoid double-spawning the fetcher. `monthly_review`
@@ -1085,6 +1159,81 @@ export class ScheduledTaskRunner {
1085
1159
  };
1086
1160
  }
1087
1161
  }
1162
+ // Deterministic pre-step for `routine.today_refresh`. rotateDayFiles()
1163
+ // intentionally leaves today.md absent at the day boundary for the
1164
+ // morning routine to recreate; when that routine has not yet run (or
1165
+ // failed — e.g. a quota/budget death with no fallback), today.md is
1166
+ // missing and this section-only refresh would 404 on its PATCH and
1167
+ // fall back to budget-burning full-file PUTs against the strict
1168
+ // validateTodayContent schema (the "Refresh Today does nothing"
1169
+ // symptom). Guarantee the canonical skeleton exists (lock-aware,
1170
+ // absent-only) so the refresh session has a valid PATCH target; full
1171
+ // creation/repair stays the morning routine's job. See
1172
+ // ensureTodaySkeleton. Skipped when no today-write-lock was wired.
1173
+ if (this.todayWriteLock
1174
+ && isRoutineEvent(effectiveEvent)
1175
+ && effectiveEvent.routine === "today_refresh") {
1176
+ // Failure-isolated like the sibling pre-steps below: this seed is a
1177
+ // best-effort convenience so the refresh has a valid PATCH target.
1178
+ // A throw here (e.g. getContextDir / serializer-lock internals) must
1179
+ // NOT abort the whole today_refresh — that would reproduce the exact
1180
+ // "Refresh Today does nothing" symptom the skeleton was added to
1181
+ // prevent. The morning routine remains responsible for real repair.
1182
+ try {
1183
+ await ensureTodaySkeleton({
1184
+ contextDir: getContextDir(this.config, this.db),
1185
+ todayWriteLock: this.todayWriteLock,
1186
+ });
1187
+ }
1188
+ catch (err) {
1189
+ logger.warn({ err }, "Failed to seed today.md skeleton for today_refresh");
1190
+ }
1191
+ }
1192
+ // FEEDBACK_LEARNING_LOOP_DESIGN.md §4 — deterministic consolidation
1193
+ // pre-step for `routine.evening_review`. Runs synchronously BEFORE
1194
+ // `contextBuilder.build` so the `<feedback_worksheet>` (unconsumed
1195
+ // signals grouped by scope + per-candidate promotion verdicts +
1196
+ // eviction ranking + consume ids) lands in the review session's
1197
+ // context. Failure-isolated inside `prepareFeedbackWorksheet`: a throw
1198
+ // there is swallowed and the review still ships. Skipped when feedback
1199
+ // learning is off or no signals pend (no empty block in the prompt).
1200
+ if (isRoutineEvent(effectiveEvent)
1201
+ && effectiveEvent.routine === "evening_review") {
1202
+ const worksheetBlock = this.prepareFeedbackWorksheet();
1203
+ if (worksheetBlock) {
1204
+ effectiveEvent = {
1205
+ ...effectiveEvent,
1206
+ data: {
1207
+ ...effectiveEvent.data,
1208
+ feedbackWorksheetBlock: worksheetBlock,
1209
+ },
1210
+ };
1211
+ }
1212
+ }
1213
+ // FEEDBACK_LEARNING_LOOP_DESIGN.md §4 "Monthly re-generalization" /
1214
+ // Phase 5 — deterministic pre-step for `routine.monthly_review`. Re-reads
1215
+ // the consolidated lesson stores (global + per-agent) and stamps a
1216
+ // `<feedback_regeneralization>` block so the monthly session collapses
1217
+ // same-theme lessons into higher-level principles. Distinct from the
1218
+ // nightly evening-review pass: it carries no signals and consumes
1219
+ // nothing — it only surfaces existing lessons ranked for collapse.
1220
+ // Failure-isolated inside `prepareRegeneralizationWorksheet`; skipped
1221
+ // when feedback learning is off or no scope has enough lessons. Rides the
1222
+ // same `monthlyReviewEnabled` kill switch as the rest of the monthly
1223
+ // routine (the routine never dispatches while it is off).
1224
+ if (isRoutineEvent(effectiveEvent)
1225
+ && effectiveEvent.routine === "monthly_review") {
1226
+ const regeneralizationBlock = this.prepareRegeneralizationWorksheet();
1227
+ if (regeneralizationBlock) {
1228
+ effectiveEvent = {
1229
+ ...effectiveEvent,
1230
+ data: {
1231
+ ...effectiveEvent.data,
1232
+ regeneralizationBlock,
1233
+ },
1234
+ };
1235
+ }
1236
+ }
1088
1237
  // WEEKLY_INTERESTS_REFLECTION_PLAN.md §10.4 — deterministic
1089
1238
  // pre-hook for `routine.weekly_review`. Runs synchronously
1090
1239
  // BEFORE `contextBuilder.build` so the freshly-refreshed
@@ -1097,6 +1246,36 @@ export class ScheduledTaskRunner {
1097
1246
  if (isRoutineEvent(effectiveEvent)
1098
1247
  && effectiveEvent.routine === "weekly_review") {
1099
1248
  this.runWeeklyInterestsReflectionPreHook(effectiveEvent);
1249
+ // SELF_TUNING_REVIEW_CYCLE_DESIGN.md §3.1 + §3.2 / Phases 1–2 —
1250
+ // deterministic Measure + Recommend pre-steps for
1251
+ // `routine.weekly_review`. Run synchronously BEFORE
1252
+ // `contextBuilder.build` so the `<self_performance>` block (7-day
1253
+ // per-routine run/cost/duration aggregates + fetch_window empty-run
1254
+ // rates + hourly-gate stage distribution + notification reaction
1255
+ // breakdown + self-tuning ledger) and the `<tuning_recommendations>`
1256
+ // block (Phase 2 rule-table output for the Phase 3c verdict step)
1257
+ // land in the weekly session's context. Both share one gather pass;
1258
+ // each is independently failure-isolated inside
1259
+ // `prepareSelfTuningBlocks` — a throw is swallowed and the review
1260
+ // still ships. Either block is omitted entirely when empty (no
1261
+ // empty XML in the prompt).
1262
+ const selfTuning = this.prepareSelfTuningBlocks();
1263
+ if (selfTuning.selfPerformanceBlock || selfTuning.tuningRecommendationsBlock) {
1264
+ effectiveEvent = {
1265
+ ...effectiveEvent,
1266
+ data: {
1267
+ ...effectiveEvent.data,
1268
+ ...(selfTuning.selfPerformanceBlock
1269
+ ? { selfPerformanceBlock: selfTuning.selfPerformanceBlock }
1270
+ : {}),
1271
+ ...(selfTuning.tuningRecommendationsBlock
1272
+ ? {
1273
+ tuningRecommendationsBlock: selfTuning.tuningRecommendationsBlock,
1274
+ }
1275
+ : {}),
1276
+ },
1277
+ };
1278
+ }
1100
1279
  }
1101
1280
  const context = await this.contextBuilder.build(effectiveEvent);
1102
1281
  const processKey = resolveProcessKey(effectiveEvent);
@@ -1277,6 +1456,299 @@ export class ScheduledTaskRunner {
1277
1456
  this.appendWeeklyInterestsJournalLine(`interest reflection failed: ${message}`);
1278
1457
  }
1279
1458
  }
1459
+ /**
1460
+ * FEEDBACK_LEARNING_LOOP_DESIGN.md §4 — the deterministic consolidation
1461
+ * pre-step. Reads unconsumed `feedback_signals` (user + agent + `agent:<slug>`
1462
+ * scope as of Phase 4), reads each lessons store's current contents, and
1463
+ * composes the `<feedback_worksheet>` block via the pure `core/feedback/*`
1464
+ * modules. Returns the block string, or `null` when feedback learning is off,
1465
+ * nothing pends, or anything throws — so the caller simply skips stamping and
1466
+ * the evening review proceeds unchanged.
1467
+ *
1468
+ * The DB read + per-scope file read live here (the FS-/DB-heavy dispatcher
1469
+ * is coverage-excluded); the byte-deterministic worksheet composition lives
1470
+ * in `buildFeedbackWorksheet`, which is 100% unit-tested.
1471
+ */
1472
+ prepareFeedbackWorksheet() {
1473
+ if (this.config.feedbackLearningEnabled === false)
1474
+ return null;
1475
+ try {
1476
+ // Nightly retention close-out (§4 step 6 / §6): drop signal rows that
1477
+ // were consumed past the retention horizon so the raw table stays
1478
+ // bounded. Only touches already-consumed rows — an unconsolidated
1479
+ // signal is never lost to retention. Runs once per Evening Review.
1480
+ // Guarded against a missing/NaN retention knob so a config gap degrades
1481
+ // to "skip the sweep", not "throw and abandon the whole consolidation".
1482
+ // The guard + cutoff math live in the pure, 100%-covered
1483
+ // `feedbackRetentionCutoff` helper (returns null → skip).
1484
+ const retentionCutoff = feedbackRetentionCutoff(this.config.feedbackSignalRetentionDays, Date.now());
1485
+ if (retentionCutoff) {
1486
+ sweepConsumedFeedbackSignals(this.db, retentionCutoff);
1487
+ }
1488
+ const groups = gatherFeedbackWorksheetScopes(this.db, {
1489
+ // Phase 4 adds `agent_slug` — per-agent (`agent:<slug>`) signals captured
1490
+ // by the explicit route + behavioral sink now fold into
1491
+ // `policies/agents/<slug>/lessons.md`. Per-scope-type fetch (not a global
1492
+ // LIMIT) keeps an `agent_slug` backlog from starving user/agent.
1493
+ scopeTypes: ["user", "agent", "agent_slug"],
1494
+ });
1495
+ if (groups.length === 0)
1496
+ return null;
1497
+ const contextDir = getContextDir(this.config, this.db);
1498
+ const byteCaps = {
1499
+ global: this.config.feedbackLessonMaxBytesGlobal,
1500
+ perAgent: this.config.feedbackLessonMaxBytesPerAgent,
1501
+ };
1502
+ const scopes = [];
1503
+ for (const group of groups) {
1504
+ const caps = lessonCapsForScope(group.scope, byteCaps);
1505
+ let existingFileMd = null;
1506
+ if (caps) {
1507
+ const rel = scopeStoreFile(group.scope);
1508
+ if (rel) {
1509
+ const full = join(contextDir, rel);
1510
+ // Per-scope read isolation (FEEDBACK_LEARNING_LOOP_DESIGN.md §11
1511
+ // v1.9 nuance #3, now closed). A *missing* file is the normal
1512
+ // first-write case (existingFileMd stays null → the LLM PUTs a
1513
+ // fresh store); but a *present-but-unreadable* file (EACCES, a
1514
+ // mid-write truncation) must NOT forfeit the whole night's
1515
+ // consolidation. Skip just this scope: its signals stay unconsumed
1516
+ // and retry next Evening Review, the file is left untouched, and the
1517
+ // user/agent scopes that read cleanly still consolidate. Crucially
1518
+ // we do NOT fall back to existingFileMd=null on a read error — that
1519
+ // would make the LLM PUT a fresh file and destroy the existing
1520
+ // lessons.
1521
+ try {
1522
+ existingFileMd = existsSync(full)
1523
+ ? readFileSync(full, "utf-8")
1524
+ : null;
1525
+ }
1526
+ catch (err) {
1527
+ logger.warn({ err, path: rel }, "Skipping feedback scope — lessons file present but unreadable; its signals will retry next Evening Review");
1528
+ continue;
1529
+ }
1530
+ }
1531
+ }
1532
+ scopes.push({
1533
+ scope: group.scope,
1534
+ signals: group.signals,
1535
+ existingFileMd,
1536
+ caps,
1537
+ });
1538
+ }
1539
+ if (scopes.length === 0)
1540
+ return null;
1541
+ const result = buildFeedbackWorksheet(scopes, {
1542
+ promotionThreshold: this.config.feedbackPromotionThreshold,
1543
+ nowIso: new Date().toISOString(),
1544
+ staleDays: this.config.feedbackLessonStaleDays,
1545
+ });
1546
+ return result?.block ?? null;
1547
+ }
1548
+ catch (err) {
1549
+ logger.warn({ err }, "Failed to prepare feedback worksheet");
1550
+ return null;
1551
+ }
1552
+ }
1553
+ /**
1554
+ * SELF_TUNING_REVIEW_CYCLE_DESIGN.md §3.1 + §3.2 / Phases 1–2 — the
1555
+ * deterministic Measure + Recommend pre-steps. Computes the 7-day window +
1556
+ * 7-day-prior baseline SQL aggregates over `agent_actions` /
1557
+ * `notification_log` / the `runtime_state.self_tuning:*` ledger, reads each
1558
+ * lesson store's byte pressure (§3.5), and composes the
1559
+ * `<self_performance>` block via the pure
1560
+ * `core/feedback/self-performance-prep.ts` module. The same gathered data
1561
+ * then feeds the Phase 2 rule table (`core/feedback/tuning-recommender.ts`):
1562
+ * the resulting pending cycle is persisted to
1563
+ * `runtime_state.self_tuning.pending_cycle` — overwriting (and thereby
1564
+ * expiring, §3.4 single-use ids) the previous cycle even when this week
1565
+ * produced zero recommendations — and rendered as the
1566
+ * `<tuning_recommendations>` block for the Phase 3c verdict step.
1567
+ *
1568
+ * Either field is `null` when there is nothing to inject or its step threw —
1569
+ * the caller simply skips stamping and the weekly review proceeds
1570
+ * unchanged. The Recommend step is failure-isolated from the Measure step:
1571
+ * a recommender throw never drops the `<self_performance>` block.
1572
+ *
1573
+ * The DB handle + lesson-file FS reads live here (the dispatcher is
1574
+ * coverage-excluded); the byte-deterministic aggregation, rule table, and
1575
+ * rendering live in the pure modules, which are 100% unit-tested. Unlike
1576
+ * the consolidation/re-generalization pre-steps this is NOT gated on
1577
+ * `feedbackLearningEnabled` — it measures core daemon telemetry, not the
1578
+ * lesson loop; nor on `selfTuningEnabled` — that flag gates Phase 3
1579
+ * *actuation* only, while recommendation generation + verdict recording IS
1580
+ * the Phase 2 shadow period (§7). An unreadable lesson store only drops the
1581
+ * `<lesson_stores>` rows / the R5 input, never the whole block.
1582
+ */
1583
+ prepareSelfTuningBlocks() {
1584
+ try {
1585
+ const now = new Date();
1586
+ const data = gatherSelfPerformanceData(this.db, { now });
1587
+ const lessonStores = [];
1588
+ const contextDir = getContextDir(this.config, this.db);
1589
+ const readStoreFile = (rel) => {
1590
+ const full = join(contextDir, rel);
1591
+ if (!existsSync(full))
1592
+ return null;
1593
+ try {
1594
+ return readFileSync(full, "utf-8");
1595
+ }
1596
+ catch (err) {
1597
+ logger.warn({ err, path: rel }, "Skipping lesson store in self-performance block — file present but unreadable");
1598
+ return null;
1599
+ }
1600
+ };
1601
+ const globalMd = readStoreFile(CONTEXT_RELATIVE_PATHS.agentLessons);
1602
+ if (globalMd !== null) {
1603
+ lessonStores.push(summarizeLessonStoreUtilization("agent", globalMd, this.config.feedbackLessonMaxBytesGlobal));
1604
+ }
1605
+ const agentsDir = join(contextDir, "policies", "agents");
1606
+ if (existsSync(agentsDir)) {
1607
+ for (const entry of readdirSync(agentsDir, { withFileTypes: true })) {
1608
+ if (!entry.isDirectory() || !isSafeAgentSlug(entry.name))
1609
+ continue;
1610
+ const md = readStoreFile(agentLessonsPath(entry.name));
1611
+ if (md === null)
1612
+ continue;
1613
+ lessonStores.push(summarizeLessonStoreUtilization(`agent:${entry.name}`, md, this.config.feedbackLessonMaxBytesPerAgent));
1614
+ }
1615
+ }
1616
+ const selfPerformanceBlock = buildSelfPerformanceBlock(data, {
1617
+ generatedAt: now.toISOString(),
1618
+ lessonStores,
1619
+ });
1620
+ // Phase 2 Recommend step — isolated so a recommender failure cannot
1621
+ // cost the weekly session its Measure block.
1622
+ let tuningRecommendationsBlock = null;
1623
+ try {
1624
+ const recommendations = buildTuningRecommendations({
1625
+ data,
1626
+ knobs: {
1627
+ activityScanPrePassFreshnessMinutes: this.config.activityScanPrePassFreshnessMinutes,
1628
+ activityScanLowSignalPendingCeiling: this.config.activityScanLowSignalPendingCeiling,
1629
+ feedbackLessonMaxBytesGlobal: this.config.feedbackLessonMaxBytesGlobal,
1630
+ },
1631
+ lessonStores,
1632
+ failingSchedules: gatherFailingRecurringSchedules(this.db),
1633
+ now,
1634
+ });
1635
+ // Read the outgoing blob before overwriting: a SAME-day re-run
1636
+ // (manual `!run` / crash retry) regenerates the same cycle id and
1637
+ // ids, and verdicts already recorded against them must survive —
1638
+ // otherwise the re-run session re-verdicts judged ids and
1639
+ // double-posts rejection self_critique signals. A prior-day blob
1640
+ // is ignored inside createPendingTuningCycle (§3.4 expiry).
1641
+ const previousCycle = readRuntimeState(this.db, TUNING_PENDING_CYCLE_STATE_KEY);
1642
+ const cycle = createPendingTuningCycle(recommendations, now.toISOString(), previousCycle);
1643
+ // Always persist — an empty cycle still expires last week's
1644
+ // single-use verdict ids (§3.4).
1645
+ writeRuntimeState(this.db, TUNING_PENDING_CYCLE_STATE_KEY, cycle);
1646
+ // Phase 3 — the block's `mode` attribute tells the weekly session
1647
+ // whether apply verdicts actuate (`live`) or are recorded only
1648
+ // (`shadow`), so the task-flow never has to guess the flag state.
1649
+ tuningRecommendationsBlock = renderTuningRecommendationsBlock(cycle, {
1650
+ mode: this.config.selfTuningEnabled === true ? "live" : "shadow",
1651
+ });
1652
+ }
1653
+ catch (err) {
1654
+ logger.warn({ err }, "Failed to prepare tuning recommendations");
1655
+ }
1656
+ return { selfPerformanceBlock, tuningRecommendationsBlock };
1657
+ }
1658
+ catch (err) {
1659
+ logger.warn({ err }, "Failed to prepare self-performance block");
1660
+ return { selfPerformanceBlock: null, tuningRecommendationsBlock: null };
1661
+ }
1662
+ }
1663
+ /**
1664
+ * FEEDBACK_LEARNING_LOOP_DESIGN.md §4 "Monthly re-generalization" / Phase 5 —
1665
+ * the deterministic monthly pre-step. Enumerates the consolidated lesson
1666
+ * stores on disk (the global `policies/agent-lessons.md` plus every per-agent
1667
+ * `policies/agents/<slug>/lessons.md`), reads their contents, and composes a
1668
+ * `<feedback_regeneralization>` block via the pure
1669
+ * `buildRegeneralizationWorksheet`. Returns the block, or `null` when feedback
1670
+ * learning is off, no store holds enough lessons to collapse, or anything
1671
+ * throws — so the caller simply skips stamping and the monthly review
1672
+ * proceeds unchanged.
1673
+ *
1674
+ * The FS enumeration lives here (the dispatcher is coverage-excluded); the
1675
+ * byte-deterministic composition lives in `buildRegeneralizationWorksheet`,
1676
+ * which is 100% unit-tested.
1677
+ */
1678
+ prepareRegeneralizationWorksheet() {
1679
+ if (this.config.feedbackLearningEnabled === false)
1680
+ return null;
1681
+ try {
1682
+ const contextDir = getContextDir(this.config, this.db);
1683
+ const scopes = [];
1684
+ // Per-file read isolation (FEEDBACK_LEARNING_LOOP_DESIGN.md §11 v1.9
1685
+ // nuance #3, now closed): one present-but-unreadable lessons file must
1686
+ // skip only that scope, not abandon the whole monthly collapse. A scope
1687
+ // that fails to read is simply not surfaced for re-generalization this
1688
+ // month; the file is left untouched.
1689
+ const readScopeFile = (rel) => {
1690
+ const full = join(contextDir, rel);
1691
+ if (!existsSync(full))
1692
+ return null;
1693
+ try {
1694
+ return readFileSync(full, "utf-8");
1695
+ }
1696
+ catch (err) {
1697
+ logger.warn({ err, path: rel }, "Skipping re-generalization scope — lessons file present but unreadable");
1698
+ return null;
1699
+ }
1700
+ };
1701
+ // Global `agent`-scope store.
1702
+ const globalRel = CONTEXT_RELATIVE_PATHS.agentLessons;
1703
+ const globalMd = readScopeFile(globalRel);
1704
+ if (globalMd !== null) {
1705
+ scopes.push({
1706
+ scope: { kind: "agent" },
1707
+ storeFile: globalRel,
1708
+ existingFileMd: globalMd,
1709
+ caps: {
1710
+ capBytes: this.config.feedbackLessonMaxBytesGlobal,
1711
+ maxEntries: GLOBAL_LESSON_ENTRY_CAP,
1712
+ },
1713
+ });
1714
+ }
1715
+ // Per-agent `agent:<slug>` stores under `policies/agents/<slug>/lessons.md`.
1716
+ // The slug is the directory name; the same `isSafeAgentSlug` guard the
1717
+ // inject + consolidate sides apply rejects any unsafe directory name so a
1718
+ // hand-created `..`-style dir never reaches the worksheet.
1719
+ const agentsDir = join(contextDir, "policies", "agents");
1720
+ if (existsSync(agentsDir)) {
1721
+ for (const entry of readdirSync(agentsDir, { withFileTypes: true })) {
1722
+ if (!entry.isDirectory() || !isSafeAgentSlug(entry.name))
1723
+ continue;
1724
+ const rel = agentLessonsPath(entry.name);
1725
+ const md = readScopeFile(rel);
1726
+ if (md === null)
1727
+ continue;
1728
+ scopes.push({
1729
+ scope: { kind: "agent_slug", ref: entry.name },
1730
+ storeFile: rel,
1731
+ existingFileMd: md,
1732
+ caps: {
1733
+ capBytes: this.config.feedbackLessonMaxBytesPerAgent,
1734
+ maxEntries: PER_AGENT_LESSON_ENTRY_CAP,
1735
+ },
1736
+ });
1737
+ }
1738
+ }
1739
+ if (scopes.length === 0)
1740
+ return null;
1741
+ const result = buildRegeneralizationWorksheet(scopes, {
1742
+ nowIso: new Date().toISOString(),
1743
+ staleDays: this.config.feedbackLessonStaleDays,
1744
+ });
1745
+ return result?.block ?? null;
1746
+ }
1747
+ catch (err) {
1748
+ logger.warn({ err }, "Failed to prepare feedback re-generalization worksheet");
1749
+ return null;
1750
+ }
1751
+ }
1280
1752
  /**
1281
1753
  * Append a single bullet under `## Weekly interests reflection`
1282
1754
  * inside `context/journal/agent.md`, creating the section (and the