total-recall-brain 3.44.0 → 3.45.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. package/bin/total-recall.mjs +6 -4
  2. package/docs/ARCHITECTURE.md +10 -7
  3. package/docs/reference/cli-reference.md +13 -8
  4. package/frontend/dist/assets/index-DACh1dhx.js +5613 -0
  5. package/frontend/dist/index.html +1 -1
  6. package/frontend/src/App.tsx +2 -2
  7. package/frontend/src/pages/PluginsPage.tsx +1 -1
  8. package/package.json +1 -1
  9. package/scaffold/.agent/skills/total-recall/plugins/auth/package-lock.json +7 -7
  10. package/scaffold/.agent/skills/total-recall/plugins/auth/package.json +1 -1
  11. package/scaffold/.agent/skills/total-recall/plugins/auth/plugin.json +1 -1
  12. package/scaffold/.agent/skills/total-recall/plugins/auth/src/cli-secret.mjs +20 -1
  13. package/scaffold/.agent/skills/total-recall/plugins/auth/src/secrets-store.mjs +12 -1
  14. package/scaffold/.agent/skills/total-recall/plugins/automations/BRICK.md +7 -0
  15. package/scaffold/.agent/skills/total-recall/plugins/automations/LICENSE +21 -0
  16. package/scaffold/.agent/skills/total-recall/plugins/automations/README.md +66 -0
  17. package/scaffold/.agent/skills/total-recall/plugins/automations/adapters.mjs +231 -0
  18. package/scaffold/.agent/skills/total-recall/plugins/automations/capability/index.mjs +16 -0
  19. package/scaffold/.agent/skills/total-recall/plugins/automations/cli.mjs +121 -0
  20. package/{src/core/plugin-cron.mjs → scaffold/.agent/skills/total-recall/plugins/automations/cron.mjs} +1 -3
  21. package/scaffold/.agent/skills/total-recall/plugins/automations/inventory.mjs +88 -0
  22. package/scaffold/.agent/skills/total-recall/plugins/automations/package-lock.json +7661 -0
  23. package/scaffold/.agent/skills/total-recall/plugins/automations/package.json +35 -0
  24. package/scaffold/.agent/skills/total-recall/plugins/automations/plugin.json +167 -0
  25. package/scaffold/.agent/skills/total-recall/plugins/automations/scheduler.mjs +246 -0
  26. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/DESIGN.md +42 -0
  27. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/automations.js +2 -0
  28. package/scaffold/.agent/skills/total-recall/plugins/automations/ui/card.js +273 -0
  29. package/scaffold/.agent/skills/total-recall/plugins/code-quality/commands/run-core.mjs +1 -1
  30. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/check.mjs +57 -5
  31. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/report-data.mjs +40 -14
  32. package/scaffold/.agent/skills/total-recall/plugins/code-quality/core/report.mjs +15 -40
  33. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package-lock.json +7 -7
  34. package/scaffold/.agent/skills/total-recall/plugins/code-quality/package.json +1 -1
  35. package/scaffold/.agent/skills/total-recall/plugins/code-quality/plugin.json +1 -1
  36. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package-lock.json +7 -7
  37. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/package.json +1 -1
  38. package/scaffold/.agent/skills/total-recall/plugins/composable-cli/plugin.json +1 -1
  39. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package-lock.json +7 -7
  40. package/scaffold/.agent/skills/total-recall/plugins/embeddings/package.json +1 -1
  41. package/scaffold/.agent/skills/total-recall/plugins/embeddings/plugin.json +1 -1
  42. package/scaffold/.agent/skills/total-recall/plugins/git/README.md +112 -0
  43. package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/cli.mjs +135 -23
  44. package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/package-lock.json +7 -7
  45. package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/package.json +2 -1
  46. package/scaffold/.agent/skills/total-recall/plugins/git/plugin.json +161 -0
  47. package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/context.mjs +94 -0
  48. package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/phase3-spec-support.mjs +50 -0
  49. package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/runtime.mjs +40 -0
  50. package/scaffold/.agent/skills/total-recall/plugins/git/src/lib/spec-support.mjs +51 -0
  51. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/_support.mjs +195 -0
  52. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/auth.mjs +201 -0
  53. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/bisect.mjs +40 -0
  54. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/cli-meta.mjs +159 -0
  55. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/commit-memory.mjs +30 -0
  56. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/commit.mjs +46 -0
  57. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/fleet.mjs +156 -0
  58. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/force-push.mjs +58 -0
  59. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/group.mjs +119 -0
  60. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/handoff.mjs +59 -0
  61. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/message.mjs +22 -0
  62. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/resolve.mjs +60 -0
  63. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/rules.mjs +81 -0
  64. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/secret-firewall.mjs +66 -0
  65. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/undo.mjs +132 -0
  66. package/scaffold/.agent/skills/total-recall/plugins/git/src/ops/why.mjs +16 -0
  67. package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/ui/card.js +2 -2
  68. package/scaffold/.agent/skills/total-recall/plugins/mesh/package-lock.json +7 -7
  69. package/scaffold/.agent/skills/total-recall/plugins/mesh/package.json +1 -1
  70. package/scaffold/.agent/skills/total-recall/plugins/mesh/plugin.json +1 -1
  71. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/README.md +25 -9
  72. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/BRICK.md +1 -1
  73. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/capability/index.mjs +18 -6
  74. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/cli.mjs +124 -24
  75. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package-lock.json +7 -7
  76. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/package.json +2 -2
  77. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/plugin.json +147 -14
  78. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/skills/meta-harness/SKILL.md +17 -3
  79. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/adoption.mjs +2 -2
  80. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/alerts.mjs +3 -3
  81. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/cascade.mjs +92 -18
  82. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/control.mjs +145 -0
  83. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/coordinator.mjs +42 -21
  84. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch-request.mjs +4 -2
  85. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/dispatch.mjs +33 -9
  86. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/documentation.mjs +55 -11
  87. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/executor.mjs +26 -4
  88. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/hermes.mjs +29 -1
  89. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/gate-adapters/index.mjs +1 -1
  90. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/included-usage.mjs +1 -1
  91. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/jobs.mjs +84 -0
  92. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/learner.mjs +3 -12
  93. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/maintenance.mjs +17 -0
  94. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/mcp-server.mjs +228 -0
  95. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/process-manager.mjs +25 -2
  96. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/reasoning.mjs +1 -1
  97. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/remote-operation.mjs +5 -1
  98. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/review.mjs +34 -3
  99. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/reviews.mjs +25 -0
  100. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/router.mjs +1 -1
  101. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/claude.mjs +319 -0
  102. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/codex.mjs +387 -0
  103. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/result.mjs +67 -0
  104. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/resume.mjs +373 -0
  105. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session/run.mjs +223 -0
  106. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/session-jobs.mjs +72 -0
  107. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/setup.mjs +6 -3
  108. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/termination.mjs +16 -12
  109. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/threads.mjs +32 -1
  110. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/usage-queue.mjs +1 -1
  111. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/verify-ssss.mjs +3 -3
  112. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worker.mjs +285 -40
  113. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/src/worktree.mjs +76 -9
  114. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/DESIGN.md +5 -5
  115. package/scaffold/.agent/skills/total-recall/plugins/meta-harness/ui/card.js +3 -3
  116. package/scaffold/.agent/skills/total-recall/plugins/openwiki/cli.mjs +47 -18
  117. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package-lock.json +7 -7
  118. package/scaffold/.agent/skills/total-recall/plugins/openwiki/package.json +1 -1
  119. package/scaffold/.agent/skills/total-recall/plugins/openwiki/plugin.json +1 -1
  120. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh-diagnostics.mjs +44 -0
  121. package/scaffold/.agent/skills/total-recall/plugins/openwiki/src/refresh.mjs +120 -113
  122. package/scaffold/.agent/skills/total-recall/plugins/security/package-lock.json +7 -7
  123. package/scaffold/.agent/skills/total-recall/plugins/security/package.json +1 -1
  124. package/scaffold/.agent/skills/total-recall/plugins/security/plugin.json +1 -1
  125. package/scaffold/.agent/skills/total-recall/references/operations.md +1 -1
  126. package/src/cli/agent.mjs +1 -1
  127. package/src/cli/command.mjs +41 -2
  128. package/src/cli/dream.mjs +93 -69
  129. package/src/cli/harness.mjs +4 -4
  130. package/src/cli/help.mjs +1 -1
  131. package/src/cli/init.mjs +0 -25
  132. package/src/cli/lessons.mjs +51 -0
  133. package/src/cli/memory-plugin.mjs +1 -1
  134. package/src/cli/plugin/copies.mjs +64 -14
  135. package/src/cli/plugin/create.mjs +6 -7
  136. package/src/cli/plugin/index.mjs +11 -7
  137. package/src/cli/plugin/install.mjs +10 -15
  138. package/src/cli/plugin/remove.mjs +1 -1
  139. package/src/cli/plugin/search.mjs +2 -2
  140. package/src/cli/preflight.mjs +19 -6
  141. package/src/cli/proposals.mjs +1 -1
  142. package/src/cli/recall.mjs +66 -23
  143. package/src/cli/remember.mjs +3 -14
  144. package/src/cli/server.mjs +18 -0
  145. package/src/cli/startup.mjs +1 -0
  146. package/src/cli/task.mjs +9 -2
  147. package/src/cli/update.mjs +6 -2
  148. package/src/core/access-ledger.mjs +78 -0
  149. package/src/core/agent-manager.mjs +2 -2
  150. package/src/core/brain-locate.mjs +15 -0
  151. package/src/core/brain-updates.mjs +31 -11
  152. package/src/core/claude-hooks.mjs +3 -1
  153. package/src/core/cleanup-task.mjs +15 -0
  154. package/src/core/cli-commands.mjs +2 -0
  155. package/src/core/cli-maintenance.mjs +4 -1
  156. package/src/core/cli-semantic-index.mjs +23 -3
  157. package/src/core/context-compiler.mjs +20 -0
  158. package/src/core/daemon-loop.mjs +39 -62
  159. package/src/core/default-plugins.mjs +1 -1
  160. package/src/core/dream.mjs +33 -105
  161. package/src/core/ensure-dependencies.mjs +148 -0
  162. package/src/core/fast-recall.mjs +2 -2
  163. package/src/core/field-evaluation.mjs +23 -0
  164. package/src/core/gate-mode.mjs +1 -1
  165. package/src/core/harness-hooks.mjs +14 -2
  166. package/src/core/lesson-hooks.mjs +74 -0
  167. package/src/core/lesson-memory.mjs +24 -0
  168. package/src/core/lesson-sessions.mjs +52 -0
  169. package/src/core/lessons.mjs +96 -0
  170. package/src/core/meta-harness.mjs +2 -2
  171. package/src/core/optimizer.mjs +66 -165
  172. package/src/core/plugin-contracts/card.mjs +3 -0
  173. package/src/core/plugin-loader.mjs +12 -3
  174. package/src/core/plugin-runtime.mjs +40 -1
  175. package/src/core/plugin-store.mjs +35 -23
  176. package/src/core/post-mortem.mjs +52 -36
  177. package/src/core/preflight-gate.mjs +23 -2
  178. package/src/core/proposal-applier.mjs +31 -2
  179. package/src/core/repo-sync.mjs +4 -6
  180. package/src/core/required-plugins.mjs +26 -4
  181. package/src/core/rule-length.mjs +23 -0
  182. package/src/core/runtime.mjs +1 -1
  183. package/src/core/scheduler.mjs +35 -43
  184. package/src/core/schema.mjs +1 -2
  185. package/src/core/search.mjs +4 -3
  186. package/src/core/server-service.mjs +148 -0
  187. package/src/core/session-watcher.mjs +19 -1
  188. package/src/core/skill-gap.mjs +16 -0
  189. package/src/core/ssss-kernel-bridge.mjs +32 -4
  190. package/src/core/startup-health.mjs +6 -1
  191. package/src/core/surface.mjs +65 -54
  192. package/src/core/task-executors.mjs +54 -106
  193. package/src/core/validated-write.mjs +1 -0
  194. package/src/core/vector-field.mjs +134 -192
  195. package/src/server/invoke-core-child.mjs +24 -0
  196. package/src/server/invoke-core-operations.mjs +102 -0
  197. package/src/server/route-manifest.json +0 -56
  198. package/src/server/routes/brains.mjs +0 -18
  199. package/src/server/routes/context.mjs +0 -41
  200. package/src/server/routes/invoke.mjs +26 -1
  201. package/src/server/routes/ssss.mjs +0 -169
  202. package/src/server/routes/system.mjs +0 -15
  203. package/src/server/routes/tasks.mjs +0 -26
  204. package/src/server/routes/vault.mjs +2 -23
  205. package/frontend/dist/assets/index-DD4qONvH.js +0 -5607
  206. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/README.md +0 -53
  207. package/scaffold/.agent/skills/total-recall/plugins/git-sentinel/plugin.json +0 -129
  208. package/src/core/append-log.mjs +0 -131
  209. package/src/core/plugin-tasks.mjs +0 -173
  210. package/templates/onboarding-interview.md +0 -82
  211. /package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/LICENSE +0 -0
  212. /package/scaffold/.agent/skills/total-recall/plugins/{git-sentinel → git}/ui/DESIGN.md +0 -0
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Memory access ledger (DECAY-01).
3
+ *
4
+ * Records when a memory is actually used: returned by recall or included in a context
5
+ * capsule. Decay reads it to tell a memory nobody uses from one read every day. It lives in
6
+ * the brain's derived state, so a read never rewrites a vault file.
7
+ */
8
+ import fs from 'node:fs';
9
+ import path from 'node:path';
10
+ import { atomicWrite } from './vault.mjs';
11
+
12
+ const LEDGER = 'access-ledger.json';
13
+
14
+ function ledgerFile(derivedDir) {
15
+ return path.join(derivedDir, LEDGER);
16
+ }
17
+
18
+ /** The ledger as `{ slug: { last_accessed, count } }`. A missing or damaged file is an empty ledger. */
19
+ export function readAccess(derivedDir) {
20
+ if (!derivedDir) return {};
21
+ try {
22
+ const value = JSON.parse(fs.readFileSync(ledgerFile(derivedDir), 'utf8'));
23
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
24
+ } catch {
25
+ return {};
26
+ }
27
+ }
28
+
29
+ /** Reserved ledger key: the days on which this brain was used at all (YYYY-MM-DD, sorted). */
30
+ export const ACTIVE_DAYS = '_active_days';
31
+
32
+ /** The days this brain was used, oldest first. */
33
+ export function activeDays(ledger = {}) {
34
+ return Array.isArray(ledger[ACTIVE_DAYS]) ? ledger[ACTIVE_DAYS] : [];
35
+ }
36
+
37
+ /**
38
+ * How many of the brain's active days came after `sinceMs`. Decay counts these, never calendar
39
+ * days, so a brain nobody works in never decays (Greg, 2026-11-09).
40
+ */
41
+ export function activeDaysSince(ledger, sinceMs) {
42
+ const since = new Date(sinceMs).toISOString().slice(0, 10);
43
+ return activeDays(ledger).filter(day => day > since).length;
44
+ }
45
+
46
+ /** Record one use of each slug, and today as an active day. Never throws: a failed write must not break recall or preflight. */
47
+ export function recordAccess(derivedDir, slugs, now = new Date()) {
48
+ const unique = [...new Set((slugs || []).filter(slug => typeof slug === 'string' && slug && slug !== ACTIVE_DAYS))];
49
+ if (!derivedDir || unique.length === 0) return false;
50
+ try {
51
+ const ledger = readAccess(derivedDir);
52
+ const stamp = now.toISOString();
53
+ const today = stamp.slice(0, 10);
54
+ const days = activeDays(ledger);
55
+ if (!days.includes(today)) ledger[ACTIVE_DAYS] = [...days, today].sort();
56
+ for (const slug of unique) {
57
+ const entry = ledger[slug] || { count: 0 };
58
+ // Three active days are enough for the field's first and second derivatives.
59
+ const daily = { ...entry.daily_counts, [today]: (Number(entry.daily_counts?.[today]) || 0) + 1 };
60
+ const recentDays = activeDays(ledger).slice(-3);
61
+ ledger[slug] = { last_accessed: stamp, count: (Number(entry.count) || 0) + 1,
62
+ daily_counts: Object.fromEntries(Object.entries(daily).filter(([day]) => recentDays.includes(day))) };
63
+ }
64
+ fs.mkdirSync(derivedDir, { recursive: true });
65
+ atomicWrite(ledgerFile(derivedDir), JSON.stringify(ledger));
66
+ return true;
67
+ } catch {
68
+ return false;
69
+ }
70
+ }
71
+
72
+ /** The latest known use of a memory: the ledger, its own `last_accessed`, or its last update. */
73
+ export function lastUsedMs(node, ledger = {}) {
74
+ const times = [ledger[node.slug]?.last_accessed, node.last_accessed, node.updated, node.created]
75
+ .map(value => new Date(value || 0).getTime())
76
+ .filter(Number.isFinite);
77
+ return times.length ? Math.max(...times) : 0;
78
+ }
@@ -1,11 +1,11 @@
1
- /** Compatibility facade; process lifecycle, isolation, metadata and receipts belong to Meta Harness. */
1
+ /** Compatibility facade; process lifecycle, isolation, metadata and receipts belong to Metaharness. */
2
2
  import { loadPluginModule } from './plugin-runtime.mjs';
3
3
 
4
4
  let owner, ownerError;
5
5
  try { owner = await loadPluginModule('meta-harness', 'process-manager'); }
6
6
  catch (error) { ownerError = error; }
7
7
  function implementation() {
8
- if (!owner) throw new Error(`Meta Harness processes are unavailable: ${ownerError?.message || 'install the plugin'}`);
8
+ if (!owner) throw new Error(`Metaharness processes are unavailable: ${ownerError?.message || 'install the plugin'}`);
9
9
  return owner;
10
10
  }
11
11
  export const loadAgentState = (...args) => implementation().loadAgentState(...args);
@@ -47,6 +47,11 @@ export function detectProjectBrain(startDir = process.cwd()) {
47
47
  // sibling in src/cli/agent-dir.mjs checked both, so the two could disagree
48
48
  // about whether a project brain exists at all. It is now the single
49
49
  // implementation and the sibling delegates here.
50
+ // The Total Recall package's own `scaffold/` is the template every install copies. It carries
51
+ // a SKILL.md, so the walk took it for a project: a context call or `project-id` from inside
52
+ // it wrote brain.json and derived ledgers into the template, which then ships (2026-11-09).
53
+ // Skip it: the project that contains the package (its checkout or the consumer) is the brain.
54
+ if (isPackageScaffold(dir)) { dir = path.dirname(dir); continue; }
50
55
  for (const name of ['.agent', '.agents']) {
51
56
  const candidate = path.join(dir, name, 'skills', 'total-recall');
52
57
  if (fs.existsSync(path.join(candidate, 'SKILL.md')) || fs.existsSync(path.join(candidate, 'memory-vault'))) {
@@ -131,3 +136,13 @@ function pluginTargets(pluginsRoot) {
131
136
  }
132
137
  return targets;
133
138
  }
139
+
140
+ /** True for the Total Recall package's install template: `<package>/scaffold`, in a checkout or in node_modules. */
141
+ export function isPackageScaffold(dir) {
142
+ if (path.basename(dir) !== 'scaffold') return false;
143
+ try {
144
+ return JSON.parse(fs.readFileSync(path.join(path.dirname(dir), 'package.json'), 'utf8')).name === 'total-recall-brain';
145
+ } catch {
146
+ return false;
147
+ }
148
+ }
@@ -116,7 +116,11 @@ export function discoverBrainConsumers(roots, { onError, installedPlugins = new
116
116
  .filter(([key]) => BRAIN_PACKAGES.includes(canonical(key)) && canonical(key) !== pkg.name)
117
117
  .map(([key, declared]) => ({ section, key, package: canonical(key), declared,
118
118
  ...installedBrainPackage(real, key) })));
119
- if (deps.length) consumers.push({ root: real, file, pkg, deps, runtimeOnly });
119
+ // A source checkout of a brain package is not a consumer: its pins move only
120
+ // with its own releases (updateSourceBrainPackage). Rewriting them left the
121
+ // cloud server's checkout dirty, and the release runner refuses a dirty one.
122
+ const brainSource = BRAIN_PACKAGES.includes(pkg.name);
123
+ if (deps.length && !brainSource) consumers.push({ root: real, file, pkg, deps, runtimeOnly });
120
124
  }
121
125
  for (const entry of fs.readdirSync(real, { withFileTypes: true })) {
122
126
  if (!entry.isDirectory() || ignored.has(entry.name)) continue;
@@ -191,13 +195,18 @@ function targetSpec(key, version) {
191
195
  return key === '@ssss/cli' ? `npm:@gregiteen/ssss-cli@${version}` : version;
192
196
  }
193
197
 
194
- /** Dry by default. Resolve releases once; install independently; verify actual resolution. */
195
198
  /**
196
- * A checkout that keeps no copy of a dev dependency runs under the shared install, so it has no
197
- * old version to replace. Its pin stays current, and its dev tree is its owner's to install: one
198
- * plugin's tree is about 460 MB, so restoring them all automatically could fill a disk.
199
+ * Automatic maintenance keeps each checkout's tree as found. A dependency with no installed
200
+ * copy, in a checkout (and owner) with no node_modules at all, gets its pin updated and nothing
201
+ * more: the tree was reclaimed for disk space or never installed, and restoring it is the
202
+ * owner's call. A dev dependency absent from a shared install is the same case. Without this,
203
+ * every hourly run reinstalled full trees into idle checkouts (~10 GB on 2026-11-07, again on
204
+ * 2026-11-09 until the disk filled). `installMissing: true` (CLI --install-missing) opts in.
199
205
  */
200
206
  const absentDevDep = (dep) => dep.section === 'devDependencies' && !dep.file;
207
+ const noInstallTree = (...roots) => roots.every(root => !fs.existsSync(path.join(root, 'node_modules')));
208
+ const keepAsFound = (dep, installMissing, ...roots) =>
209
+ !installMissing && !dep.file && (absentDevDep(dep) || noInstallTree(...roots));
201
210
 
202
211
  /**
203
212
  * The daemon runs with NODE_ENV=production, and under that npm and pnpm remove dev dependencies
@@ -210,6 +219,7 @@ export function installEnv(env = process.env) {
210
219
 
211
220
  export async function updateAllBrains(opts = {}) {
212
221
  const dryRun = opts.dryRun !== false;
222
+ const installMissing = opts.installMissing === true;
213
223
  const exec = opts.exec || spawnSync;
214
224
  const roots = listUpdateRoots(opts).map(row => row.root);
215
225
  const errors = [];
@@ -270,12 +280,16 @@ export async function updateAllBrains(opts = {}) {
270
280
  const shared = isLink(path.join(owner.owner, 'node_modules')) || isLink(path.join(consumer.root, 'node_modules'));
271
281
  const needsChange = shared || (!consumer.runtimeOnly && overridesLag(consumer.pkg, versions)) || consumer.deps.some(dep => {
272
282
  const pinLags = !consumer.runtimeOnly && dep.declared !== targetSpec(dep.key, versions[dep.package]);
273
- if (absentDevDep(dep)) return pinLags;
283
+ if (keepAsFound(dep, installMissing, consumer.root, owner.owner)) return pinLags;
274
284
  return pinLags || dep.version !== versions[dep.package]
275
285
  || !dep.file?.startsWith(path.join(owner.owner, 'node_modules') + path.sep);
276
286
  });
277
- if (!needsChange) continue;
278
- row.status = dryRun ? 'would_update' : 'pending';
287
+ if (!needsChange) {
288
+ // Kept as found: report the missing tree, install nothing.
289
+ if (consumer.deps.some(dep => !dep.version)) row.status = 'not_installed';
290
+ continue;
291
+ }
292
+ row.status = dryRun ? (consumer.deps.some(dep => !dep.version) ? 'not_installed' : 'would_update') : 'pending';
279
293
  if (!groups.has(owner.owner)) groups.set(owner.owner, { ...owner, consumers: [] });
280
294
  groups.get(owner.owner).consumers.push({ consumer, row });
281
295
  }
@@ -290,7 +304,8 @@ export async function updateAllBrains(opts = {}) {
290
304
  const runtimeOnly = group.consumers.every(({ consumer }) => consumer.runtimeOnly);
291
305
  const unpinned = runtimeOnly && group.manager === 'npm';
292
306
  const needsInstall = group.consumers.some(({ consumer }) =>
293
- isLink(path.join(consumer.root, 'node_modules')) || consumer.deps.some(dep => !absentDevDep(dep)
307
+ isLink(path.join(consumer.root, 'node_modules')) || consumer.deps.some(dep =>
308
+ !keepAsFound(dep, installMissing, consumer.root, group.owner)
294
309
  && (dep.version !== versions[dep.package] || !dep.file?.startsWith(path.join(group.owner, 'node_modules') + path.sep))));
295
310
  try {
296
311
  lockFd = acquireBrainUpdateLock(lock, opts.lockWaitMs === undefined ? {} : { waitMs: opts.lockWaitMs });
@@ -322,7 +337,10 @@ export async function updateAllBrains(opts = {}) {
322
337
  if (runtimeOnly) args.push(group.manager === 'pnpm' ? '--prod' : '--omit=dev');
323
338
  if (unpinned) args.push('--no-save', ...new Set(group.consumers.flatMap(({ consumer }) =>
324
339
  consumer.deps.map(dep => `${dep.key}@${targetSpec(dep.key, versions[dep.package])}`))));
325
- else if (!needsInstall) args.push(group.manager === 'pnpm' ? '--lockfile-only' : '--package-lock-only');
340
+ // A lockfile-only update builds no tree, so it runs no lifecycle scripts: npm still
341
+ // ran a checkout's own postinstall here, which fails in a repository that was never
342
+ // installed (a postinstall calling a tool that is not yet installed, 2026-11-09).
343
+ else if (!needsInstall) args.push(group.manager === 'pnpm' ? '--lockfile-only' : '--package-lock-only', '--ignore-scripts');
326
344
  const install = exec(group.manager, args, { cwd: group.owner, env: installEnv(), encoding: 'utf8',
327
345
  timeout: 600000, maxBuffer: 8 * 1024 * 1024 });
328
346
  if (install.status !== 0) throw new Error(install.error?.message || install.stderr?.trim().slice(-1500) || `Install exited ${install.status}`);
@@ -338,7 +356,7 @@ export async function updateAllBrains(opts = {}) {
338
356
  const placed = (file) => Boolean(file) && allowedRoots.some((root) => file.startsWith(root));
339
357
  const bad = consumer.deps.find(dep => {
340
358
  const actual = installedBrainPackage(consumer.root, dep.key);
341
- if (absentDevDep(dep) && !actual.file) return false;
359
+ if (keepAsFound(dep, installMissing, consumer.root, group.owner) && !actual.file) return false;
342
360
  return actual.version !== versions[dep.package] || !placed(actual.file);
343
361
  });
344
362
  // A link is only a fault when it escapes every legitimate root; a self-contained one is fine.
@@ -368,5 +386,7 @@ export async function updateAllBrains(opts = {}) {
368
386
  updated: results.filter(row => row.status === 'updated').length,
369
387
  shared_install: results.filter(row => row.status === 'shared_install').length,
370
388
  would_update: results.filter(row => row.status === 'would_update').length,
389
+ not_installed: results.filter(row => row.status === 'not_installed').length,
390
+ up_to_date: results.filter(row => row.status === 'up_to_date').length,
371
391
  failed: results.filter(row => row.status === 'failed').length, results };
372
392
  }
@@ -12,6 +12,7 @@ import fs from 'node:fs';
12
12
  import os from 'node:os';
13
13
  import path from 'node:path';
14
14
  import { fileURLToPath } from 'node:url';
15
+ import { installLessonHooks } from './lesson-hooks.mjs';
15
16
 
16
17
  const PACKAGE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', '..');
17
18
  export const GATE_TEMPLATE = path.join(PACKAGE_ROOT, 'templates', 'claude-hooks', 'total-recall-gate.mjs');
@@ -104,5 +105,6 @@ export function installClaudeCodeHooks(opts = {}) {
104
105
  if (fs.existsSync(settingsPath)) fs.copyFileSync(settingsPath, `${settingsPath}.bak-total-recall`);
105
106
  fs.writeFileSync(settingsPath, `${JSON.stringify(settings, null, 2)}\n`);
106
107
  }
107
- return { gate: hookPath, settings: settingsPath, added, removed };
108
+ const lessons = installLessonHooks({ harness: 'claude', settings: settingsPath, status: 'installed' }, { dryRun: opts.dryRun });
109
+ return { gate: hookPath, settings: settingsPath, added, removed, lessons };
108
110
  }
@@ -0,0 +1,15 @@
1
+ import { generateMemoryCleanupProposals, dedupeProposals, evaluateProposalGate } from './optimizer.mjs';
2
+ import { writeNode } from './vault.mjs';
3
+
4
+ /** Compaction uses the same gate, identity and review path as dream cleanup. */
5
+ export async function fileCleanupProposals(vaultDir, { generate = generateMemoryCleanupProposals,
6
+ dedupe = dedupeProposals, gate = evaluateProposalGate, write = writeNode } = {}) {
7
+ const { kept, suppressed } = dedupe(await generate(vaultDir), vaultDir);
8
+ let persisted = 0;
9
+ for (const proposal of kept) {
10
+ await gate(proposal, null, vaultDir);
11
+ await write(proposal, vaultDir);
12
+ persisted++;
13
+ }
14
+ return { success: true, persisted, suppressed, output: `Compaction filed ${persisted} cleanup proposals`, executor: 'compaction' };
15
+ }
@@ -5,10 +5,12 @@ export const CLI_COMMANDS = {
5
5
  setup: 'setup.mjs',
6
6
  start: 'start.mjs',
7
7
  startup: 'startup.mjs',
8
+ server: 'server.mjs',
8
9
  deploy: 'deploy.mjs',
9
10
  doctor: 'doctor.mjs',
10
11
  backup: 'backup.mjs',
11
12
  dream: 'dream.mjs',
13
+ lessons: 'lessons.mjs',
12
14
  lint: 'lint.mjs',
13
15
  daemon: 'daemon.mjs',
14
16
  restore: 'restore.mjs',
@@ -21,7 +21,10 @@ export function cliInputFingerprint(root) {
21
21
 
22
22
  export async function maintainCliSurfaces({brainDir,brains,force=false,run=runBounded,fingerprint=cliInputFingerprint}={}) {
23
23
  const results=[];
24
- for(const brain of brains || listLocalBrains({globalBrainDir:brainDir})) {
24
+ // By default only the global brain: entry files are static and each repository reads its
25
+ // rules and commands on demand, so the daemon never fans a refresh out across registered
26
+ // repositories (ON-DEMAND-CONTEXT-01). An explicit brain list is still honoured.
27
+ for(const brain of brains || listLocalBrains({globalBrainDir:brainDir}).filter(item=>item.kind==='global')) {
25
28
  if(!brain.exists || brain.enabled===false)continue;
26
29
  const root=brain.root || path.dirname(path.dirname(brain.brainDir));
27
30
  const file=path.join(brain.brainDir,'memory-derived/cli-maintenance.json');
@@ -22,6 +22,21 @@ import { logger } from './logger.mjs';
22
22
  import { atomicWrite } from './vault.mjs';
23
23
  import { globalAgentDir } from './local-config.mjs';
24
24
 
25
+ /** Usage text for declared plugin arguments: positional as <label>, flags as [--flag <label>] or [--flag]. */
26
+ export function formatSubcommandArgs(args) {
27
+ if (typeof args === 'string') return args ? ` ${args}` : '';
28
+ if (!Array.isArray(args)) return '';
29
+ const parts = [];
30
+ for (const arg of args) {
31
+ if (!arg || typeof arg !== 'object') continue;
32
+ const label = String(arg.name || arg.label || 'value').replace(/\s+/g, '-');
33
+ if (arg.positional || !arg.flag) parts.push(arg.required ? `<${label}>` : `[${label}]`);
34
+ else if (arg.type === 'boolean') parts.push(arg.required ? arg.flag : `[${arg.flag}]`);
35
+ else parts.push(arg.required ? `${arg.flag} <${label}>` : `[${arg.flag} <${label}>]`);
36
+ }
37
+ return parts.length ? ` ${parts.join(' ')}` : '';
38
+ }
39
+
25
40
  /** Canonical descriptions and usage signatures for built-in core commands. */
26
41
  const CORE_METADATA = {
27
42
  app: {
@@ -49,6 +64,11 @@ const CORE_METADATA = {
49
64
  usage: 'total-recall startup [check|ensure]',
50
65
  effect: 'write'
51
66
  },
67
+ server: {
68
+ description: 'Repair and verify the macOS server LaunchAgent for this package.',
69
+ usage: 'total-recall server ensure [--dry-run]',
70
+ effect: 'write'
71
+ },
52
72
  deploy: {
53
73
  description: 'Deploy host runtime layers, daemon services, or launch browser setup.',
54
74
  usage: 'total-recall deploy [--ui]',
@@ -66,7 +86,7 @@ const CORE_METADATA = {
66
86
  },
67
87
  dream: {
68
88
  description: 'Trigger memory consolidation sleep cycle.',
69
- usage: 'total-recall dream',
89
+ usage: 'total-recall dream [--all-brains] [--dry-run] [--json]',
70
90
  effect: 'write'
71
91
  },
72
92
  lint: {
@@ -320,7 +340,7 @@ const CORE_METADATA = {
320
340
  effect: 'read'
321
341
  },
322
342
  harness: {
323
- description: 'Meta-harness orchestration, task dispatch, and council consensus.',
343
+ description: 'Metaharness orchestration, task dispatch, and council consensus.',
324
344
  usage: 'total-recall harness [dispatch|council|usage]',
325
345
  effect: 'write'
326
346
  },
@@ -431,7 +451,7 @@ export function harvestAllCliVerbs({ projectRoot = process.cwd(), skillsDir = nu
431
451
  if (seenIds.has(subId)) continue;
432
452
  seenIds.add(subId);
433
453
 
434
- const argsStr = sub.args ? ` ${sub.args}` : '';
454
+ const argsStr = formatSubcommandArgs(sub.args);
435
455
  verbs.push({
436
456
  id: subId,
437
457
  name: sub.name,
@@ -1,6 +1,9 @@
1
1
  /** Local task capsules: explicit required rules, ranked supporting knowledge, complete-render accounting. */
2
2
 
3
+ import path from 'node:path';
3
4
  import { getNodes } from './vault-cache.mjs';
5
+ import { recordAccess } from './access-ledger.mjs';
6
+ import { brainDir as globalBrainDir } from './local-config.mjs';
4
7
  import crypto from 'node:crypto';
5
8
  import { selectRules, assembleContext, curatedRules, ruleFingerprint, RULE_CATEGORIES } from './context-policy.mjs';
6
9
  import { retrieveTaskKnowledge, summarizeRetrieval } from './task-context.mjs';
@@ -142,9 +145,26 @@ export async function compileContext({
142
145
  result.stats.slots = { invariants: { nodes: rules.filter(n => n.category === 'invariants').length,
143
146
  tokens: result.stats.required_tokens } };
144
147
  result.stats.slots_filled = result.stats.contributions.length;
148
+ // DECAY-01: a capsule is a real use of the rules and memories it carries.
149
+ recordCapsuleAccess(derivedDir, rules, knowledge.contributions);
145
150
  return result;
146
151
  }
147
152
 
153
+ /**
154
+ * Record each rule and memory a capsule carries in the ledger of the brain that owns it, so
155
+ * decay in the global brain sees global rules used by every project.
156
+ */
157
+ export function recordCapsuleAccess(derivedDir, rules = [], contributions = [], globalDerivedDir = path.join(globalBrainDir, 'memory-derived')) {
158
+ const owned = { project: [], global: [] };
159
+ for (const node of rules) (node._layer === 'global' ? owned.global : owned.project).push(node.slug);
160
+ for (const { id } of contributions || []) {
161
+ const match = /^vault:(global|project):(.+)$/.exec(String(id || ''));
162
+ if (match) owned[match[1]].push(match[2]);
163
+ }
164
+ if (derivedDir) recordAccess(derivedDir, owned.project);
165
+ recordAccess(derivedDir && path.resolve(derivedDir) === path.resolve(globalDerivedDir) ? derivedDir : globalDerivedDir, owned.global);
166
+ }
167
+
148
168
  /**
149
169
  * Second line of every text capsule, so output that is truncated or piped through
150
170
  * `head` still carries the rule (the rule once sat at line 544 of the output and
@@ -4,8 +4,8 @@ import { loadPluginModule as __loadOwnedPlugin } from './plugin-runtime.mjs';
4
4
  *
5
5
  * Durable worker over the open task envelope queue.
6
6
  * - Dispatches via executor registry (fail-loud on unknown)
7
- * - Does NOT invent idle work unless TR_IDLE_TASKS=1
8
- * - Periodically runs dream (system memory consolidation)
7
+ * - Automations owns scheduled maintenance; empty queues stay empty
8
+ * - Executes dream tasks scheduled by the automations plugin
9
9
  * - Ingests sessions on boot; recompiles surfaces every N ticks
10
10
  * - Auto-syncs all .md files from registered repos into Total Recall
11
11
  *
@@ -17,7 +17,7 @@ import { DAEMON_PID_FILE } from './daemon-control.mjs';
17
17
  import fs from 'fs';
18
18
  import { fileURLToPath } from 'node:url';
19
19
  import { readProcessCommand, entryPathHint, shouldHonorPidLock } from './pid-lock.mjs';
20
- import { createScheduler, updateTaskStatus, persistTaskToDisk } from './scheduler.mjs';
20
+ import { createScheduler, updateTaskStatus } from './scheduler.mjs';
21
21
  import { scanAndIngest } from './session-watcher.mjs';
22
22
  import { syncAllRepos } from './repo-sync.mjs';
23
23
  import { refreshProviderUsage } from './usage-fetcher.mjs';
@@ -26,7 +26,7 @@ import { updateQueueItem } from './research-queue.mjs';
26
26
  import { agentDir, brainDir, detectProjectBrain } from './config.mjs';
27
27
  import { runCrons } from './crons.mjs';
28
28
  import { dispatchTask as registryDispatch } from './task-executors.mjs';
29
- import { buildTaskEnvelope, publishTaskSchedulerProtocol } from './task-envelope.mjs';
29
+ import { publishTaskSchedulerProtocol } from './task-envelope.mjs';
30
30
 
31
31
  const AGENT_DIR = agentDir;
32
32
  const BRAIN_DIR = brainDir;
@@ -61,8 +61,16 @@ const RECOMPILE_EVERY_N_TASKS = 20;
61
61
  const TASK_SLEEP_MS = 10000;
62
62
  const EMPTY_SLEEP_MS = 30000;
63
63
 
64
- // Run a system dream every N empty ticks (memory consolidation without busy-work)
65
- const DREAM_EVERY_N_EMPTY = 20;
64
+ /** An empty queue only sleeps. Plugin services own recurring task creation. */
65
+ export async function nextDaemonTask(scheduler, { localOnly = false, sleep = ms => new Promise(resolve => setTimeout(resolve, ms)) } = {}) {
66
+ const next = scheduler.next({ localOnly });
67
+ if (!next.task) {
68
+ const sleepMs = localOnly ? TASK_SLEEP_MS : EMPTY_SLEEP_MS;
69
+ logger.info({ subsystem: 'daemon-loop', message: `Queue empty — sleeping ${sleepMs}ms` });
70
+ await sleep(sleepMs);
71
+ }
72
+ return next;
73
+ }
66
74
 
67
75
  function executorContext(runtimeConfig) {
68
76
  return {
@@ -242,6 +250,7 @@ let running = true;
242
250
  let vaultWatcher = null;
243
251
  let shuttingDown = false;
244
252
  let schedulerProtocolActive = false;
253
+ let lastLessonScan = 0;
245
254
 
246
255
  // Upper bound on a graceful stop. A loop iteration can be mid-task, so the
247
256
  // signal cannot exit instantly — but it must exit *eventually*.
@@ -259,7 +268,7 @@ import { releaseLease } from './leader-election.mjs';
259
268
  * daemon.log across three separate SIGTERMs. The fallback timer bounds that, so
260
269
  * even a wedged iteration cannot keep the daemon alive indefinitely.
261
270
  */
262
- let stopPluginTasks = null;
271
+ let stopPluginServices = null;
263
272
  let stopCliMaintenance = null;
264
273
  let stopAutomaticUpdates = null;
265
274
 
@@ -269,7 +278,7 @@ function shutdown(signal) {
269
278
  logger.info({ subsystem: 'daemon-loop', message: `${signal} received — shutting down gracefully` });
270
279
  running = false;
271
280
  if (vaultWatcher) vaultWatcher.stop();
272
- if (stopPluginTasks) stopPluginTasks();
281
+ if (stopPluginServices) stopPluginServices();
273
282
  stopCliMaintenance?.();
274
283
  stopAutomaticUpdates?.();
275
284
 
@@ -451,15 +460,16 @@ async function main() {
451
460
  }
452
461
  }
453
462
 
454
- let emptyTicks = 0;
455
463
 
456
- // Plugin tasks are per node (a plugin installed here runs here), so they are
457
- // scheduled on their own timer rather than inside the leader-only task loop.
464
+ // Per-node services owned by plugins (scheduled plugin tasks belong to
465
+ // automations) start here; core runs whatever active plugins declare.
458
466
  try {
459
- const { startPluginTaskScheduler } = await import('./plugin-tasks.mjs');
460
- stopPluginTasks = startPluginTaskScheduler();
467
+ const { startPluginDaemonServices } = await import('./plugin-runtime.mjs');
468
+ const services = await startPluginDaemonServices();
469
+ stopPluginServices = services.stop;
470
+ logger.info('daemon-loop', `Plugin daemon services started: ${services.started.join(', ') || 'none'}${services.failed.length ? `; failed: ${services.failed.join(', ')}` : ''}`);
461
471
  } catch (err) {
462
- logger.warn('daemon-loop', `Plugin task scheduler failed to start: ${err.message}`);
472
+ logger.warn('daemon-loop', `Plugin daemon services failed to start: ${err.message}`);
463
473
  }
464
474
 
465
475
  while (running) {
@@ -560,14 +570,20 @@ async function main() {
560
570
  }
561
571
  }
562
572
 
563
- if (!await isLeader()) {
564
- await new Promise(r => setTimeout(r, TASK_SLEEP_MS));
565
- taskCount++;
566
- continue;
573
+ const localOnly = !await isLeader();
574
+
575
+ // Local transcripts stay on their node. Followers also queue and run reviews.
576
+ if (Date.now() - lastLessonScan >= 60_000) {
577
+ lastLessonScan = Date.now();
578
+ try {
579
+ scanAndIngest(SESSIONS_DIR);
580
+ const { enqueueSessionReviews } = await import('./lessons.mjs');
581
+ enqueueSessionReviews({ brainDir: BRAIN_DIR });
582
+ } catch (error) { logger.warn('daemon-loop', `Local lesson ingest failed: ${error.message}`); }
567
583
  }
568
584
 
569
585
  // Periodically recompile surfaces (LEADER ONLY)
570
- if (taskCount > 0 && taskCount % RECOMPILE_EVERY_N_TASKS === 0) {
586
+ if (!localOnly && taskCount > 0 && taskCount % RECOMPILE_EVERY_N_TASKS === 0) {
571
587
  logger.info({ subsystem: 'daemon-loop', message: 'Running scheduled surface recompile...' });
572
588
  try {
573
589
  const { compileSurface } = await import('./surface.mjs');
@@ -581,16 +597,6 @@ async function main() {
581
597
  logger.info({ subsystem: 'daemon-loop', message: `Surface recompile error: ${err.message}` });
582
598
  }
583
599
 
584
- try {
585
- const { compactAppendLogs } = await import('./append-log.mjs');
586
- const compactResult = compactAppendLogs();
587
- if (compactResult.logs_compacted > 0) {
588
- logger.info({ subsystem: 'daemon-loop', message: `Compacted ${compactResult.logs_compacted} append logs (${compactResult.total_entries} entries)` });
589
- }
590
- } catch (err) {
591
- logger.info({ subsystem: 'daemon-loop', message: `Append log compaction error: ${err.message}` });
592
- }
593
-
594
600
  try {
595
601
  await runCrons({ vaultDir: VAULT_DIR, skillsDir: SKILLS_DIR, brainDir: BRAIN_DIR });
596
602
  } catch (err) {
@@ -627,42 +633,13 @@ async function main() {
627
633
  queueDir: QUEUE_DIR,
628
634
  vaultDir: VAULT_DIR,
629
635
  sessionsDir: SESSIONS_DIR,
636
+ localOnly,
630
637
  });
631
638
 
632
- let { task, source } = scheduler.next();
633
-
634
- // Empty queue: optional system dream, then sleep (no busy-work invention)
639
+ const { task, source } = await nextDaemonTask(scheduler, { localOnly });
635
640
  if (!task) {
636
- emptyTicks++;
637
- if (emptyTicks % DREAM_EVERY_N_EMPTY === 0) {
638
- const dreamEnv = buildTaskEnvelope({
639
- intent: 'Scheduled system dream cycle',
640
- slug: `dream-system-${Date.now().toString(36)}`,
641
- kind: 'system',
642
- executor: 'dream',
643
- category: 'memory-maintenance',
644
- priority: 70,
645
- system: true,
646
- origin: { agent: 'daemon', created_by: 'daemon-system' },
647
- capabilities: ['vault:read', 'vault:write'],
648
- });
649
- persistTaskToDisk(dreamEnv, QUEUE_DIR);
650
- task = dreamEnv;
651
- source = 'system';
652
- logger.info({
653
- subsystem: 'daemon-loop',
654
- message: `Empty queue → enqueue system dream (${emptyTicks} empty ticks)`,
655
- });
656
- } else {
657
- logger.info({
658
- subsystem: 'daemon-loop',
659
- message: `Queue empty (tick ${emptyTicks}) — sleeping ${EMPTY_SLEEP_MS}ms`,
660
- });
661
- await new Promise((r) => setTimeout(r, EMPTY_SLEEP_MS));
662
- continue;
663
- }
664
- } else {
665
- emptyTicks = 0;
641
+ if (localOnly) taskCount++;
642
+ continue;
666
643
  }
667
644
 
668
645
  taskCount++;
@@ -1,7 +1,7 @@
1
1
  /** Default runtime foundation and routine agent-development tools. */
2
2
  export const DEFAULT_PLUGIN_IDS = Object.freeze([
3
3
  'auth', 'ssss', 'mesh', 'openwiki', 'composable-cli', 'meta-harness',
4
- 'code-quality', 'security', 'embeddings', 'git-sentinel',
4
+ 'code-quality', 'security', 'embeddings', 'git', 'automations',
5
5
  ]);
6
6
 
7
7
  /** Required defaults that own additional installation payloads. */