@vellumai/assistant 0.7.0 → 0.7.1

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 (666) hide show
  1. package/ARCHITECTURE.md +6 -7
  2. package/Dockerfile +1 -0
  3. package/README.md +2 -2
  4. package/__tests__/permissions/gateway-threshold-reader.test.ts +79 -139
  5. package/bun.lock +3 -0
  6. package/docs/architecture/security.md +18 -16
  7. package/knip.json +1 -0
  8. package/node_modules/@vellumai/skill-host-contracts/__tests__/client.test.ts +1 -5
  9. package/node_modules/@vellumai/skill-host-contracts/src/assistant-event.ts +0 -5
  10. package/node_modules/@vellumai/skill-host-contracts/src/client.ts +10 -16
  11. package/node_modules/@vellumai/skill-host-contracts/src/skill-host.ts +1 -9
  12. package/node_modules/@vellumai/skill-host-contracts/src/tool-types.ts +12 -12
  13. package/node_modules/@vellumai/slack-text/bun.lock +24 -0
  14. package/node_modules/@vellumai/slack-text/package.json +18 -0
  15. package/node_modules/@vellumai/slack-text/src/index.test.ts +153 -0
  16. package/node_modules/@vellumai/slack-text/src/index.ts +235 -0
  17. package/node_modules/@vellumai/slack-text/tsconfig.json +20 -0
  18. package/openapi.yaml +294 -107
  19. package/package.json +4 -2
  20. package/scripts/generate-openapi.ts +16 -111
  21. package/src/__tests__/agent-wake-override-profile.test.ts +23 -1
  22. package/src/__tests__/anthropic-provider.test.ts +56 -13
  23. package/src/__tests__/app-conversation-ids-backfill.test.ts +278 -0
  24. package/src/__tests__/app-conversation-ids.test.ts +151 -0
  25. package/src/__tests__/approval-cascade.test.ts +0 -15
  26. package/src/__tests__/approval-routes-http.test.ts +6 -17
  27. package/src/__tests__/assistant-event-hub.test.ts +126 -77
  28. package/src/__tests__/assistant-event.test.ts +0 -5
  29. package/src/__tests__/assistant-events-sse-hardening.test.ts +37 -15
  30. package/src/__tests__/assistant-feature-flags-integration.test.ts +0 -29
  31. package/src/__tests__/background-shell-host-bash.test.ts +34 -43
  32. package/src/__tests__/call-controller.test.ts +1 -1
  33. package/src/__tests__/call-site-routing-provider.test.ts +193 -0
  34. package/src/__tests__/channel-approval-routes.test.ts +10 -296
  35. package/src/__tests__/channel-approvals.test.ts +25 -17
  36. package/src/__tests__/channel-guardian.test.ts +100 -146
  37. package/src/__tests__/checker.test.ts +20 -34
  38. package/src/__tests__/compact-event-conversation-id-guard.test.ts +50 -0
  39. package/src/__tests__/compaction-events.test.ts +2 -0
  40. package/src/__tests__/config-schema.test.ts +6 -48
  41. package/src/__tests__/config-watcher.test.ts +12 -0
  42. package/src/__tests__/connection-policy.test.ts +1 -52
  43. package/src/__tests__/contacts-write.test.ts +2 -64
  44. package/src/__tests__/context-image-dimensions.test.ts +1 -1
  45. package/src/__tests__/context-search-memory-source.test.ts +120 -1
  46. package/src/__tests__/context-search-memory-v2-source.test.ts +383 -0
  47. package/src/__tests__/context-search-pkb-source.test.ts +49 -0
  48. package/src/__tests__/context-search-workspace-source.test.ts +9 -22
  49. package/src/__tests__/context-window-manager.test.ts +46 -0
  50. package/src/__tests__/conversation-agent-loop-inference-profile.test.ts +2 -0
  51. package/src/__tests__/conversation-agent-loop-overflow.test.ts +102 -29
  52. package/src/__tests__/conversation-agent-loop.test.ts +980 -13
  53. package/src/__tests__/conversation-analysis-routes.test.ts +12 -10
  54. package/src/__tests__/conversation-attention-telegram.test.ts +11 -3
  55. package/src/__tests__/conversation-confirmation-signals.test.ts +0 -291
  56. package/src/__tests__/conversation-history-web-search.test.ts +4 -3
  57. package/src/__tests__/conversation-inference-profile-route.test.ts +12 -23
  58. package/src/__tests__/conversation-lifecycle.test.ts +4 -4
  59. package/src/__tests__/conversation-process-callsite.test.ts +79 -2
  60. package/src/__tests__/conversation-queue.test.ts +3 -8
  61. package/src/__tests__/conversation-routes-disk-view.test.ts +1 -161
  62. package/src/__tests__/conversation-routes-guardian-reply.test.ts +0 -32
  63. package/src/__tests__/conversation-routes-slash-commands.test.ts +75 -66
  64. package/src/__tests__/conversation-runtime-assembly.test.ts +257 -3
  65. package/src/__tests__/conversation-slash-commands.test.ts +24 -4
  66. package/src/__tests__/conversation-slash-queue.test.ts +2 -0
  67. package/src/__tests__/conversation-speed-override.test.ts +0 -3
  68. package/src/__tests__/conversation-starter-routes.test.ts +79 -2
  69. package/src/__tests__/conversation-surfaces-standalone-payloads.test.ts +12 -5
  70. package/src/__tests__/conversation-surfaces-standalone.test.ts +18 -14
  71. package/src/__tests__/conversation-surfaces-state-update.test.ts +3 -2
  72. package/src/__tests__/conversation-tool-setup-app-refresh.test.ts +8 -46
  73. package/src/__tests__/conversation-usage.test.ts +253 -3
  74. package/src/__tests__/credential-execution-shell-lockdown.test.ts +0 -39
  75. package/src/__tests__/credential-health-service.test.ts +68 -0
  76. package/src/__tests__/credential-security-e2e.test.ts +4 -3
  77. package/src/__tests__/credential-security-invariants.test.ts +1 -5
  78. package/src/__tests__/credential-token-resolver.test.ts +180 -0
  79. package/src/__tests__/cu-unified-flow.test.ts +33 -16
  80. package/src/__tests__/daemon-assistant-events.test.ts +34 -21
  81. package/src/__tests__/daemon-credential-client.test.ts +4 -1
  82. package/src/__tests__/db-connection-isolation.test.ts +125 -0
  83. package/src/__tests__/db-migration-rollback.test.ts +101 -0
  84. package/src/__tests__/db-slack-compaction-watermark-migration.test.ts +169 -0
  85. package/src/__tests__/deterministic-verification-control-plane.test.ts +7 -80
  86. package/src/__tests__/document-conversations.test.ts +332 -0
  87. package/src/__tests__/embedding-managed-proxy-selection.test.ts +2 -2
  88. package/src/__tests__/emit-event-signal.test.ts +4 -6
  89. package/src/__tests__/events-client-registration.test.ts +193 -49
  90. package/src/__tests__/filing-service.test.ts +58 -7
  91. package/src/__tests__/first-greeting.test.ts +156 -150
  92. package/src/__tests__/fixtures/mock-chrome-extension.ts +108 -66
  93. package/src/__tests__/get-skill-detail-audit.test.ts +3 -8
  94. package/src/__tests__/guardian-binding-drift-heal.test.ts +1 -1
  95. package/src/__tests__/guardian-dispatch.test.ts +1 -1
  96. package/src/__tests__/guardian-grant-minting.test.ts +7 -2
  97. package/src/__tests__/guardian-routing-invariants.test.ts +7 -2
  98. package/src/__tests__/guardian-routing-state.test.ts +1 -1
  99. package/src/__tests__/handlers-skills-memory-v2-reseed.test.ts +32 -11
  100. package/src/__tests__/handlers-user-message-approval-consumption.test.ts +2 -83
  101. package/src/__tests__/headless-browser-mode.test.ts +4 -9
  102. package/src/__tests__/headless-browser-navigate.test.ts +21 -20
  103. package/src/__tests__/heartbeat-service.test.ts +289 -7
  104. package/src/__tests__/helpers/channel-test-adapter.ts +2 -2
  105. package/src/__tests__/helpers/create-guardian-binding.ts +91 -0
  106. package/src/__tests__/host-bash-proxy.test.ts +46 -122
  107. package/src/__tests__/host-browser-e2e-cloud.test.ts +36 -497
  108. package/src/__tests__/host-browser-e2e-self-hosted-capability.test.ts +26 -96
  109. package/src/__tests__/host-browser-proxy.test.ts +111 -185
  110. package/src/__tests__/host-browser-routes.test.ts +45 -75
  111. package/src/__tests__/host-browser-ws-events-e2e.test.ts +26 -30
  112. package/src/__tests__/host-cu-proxy.test.ts +56 -111
  113. package/src/__tests__/host-file-proxy.test.ts +44 -98
  114. package/src/__tests__/host-file-read-tool.test.ts +42 -21
  115. package/src/__tests__/host-shell-tool.test.ts +33 -68
  116. package/src/__tests__/host-transfer-pending-interactions.test.ts +2 -18
  117. package/src/__tests__/host-transfer-proxy.test.ts +43 -53
  118. package/src/__tests__/http-user-message-parity.test.ts +0 -6
  119. package/src/__tests__/inbound-slack-persistence.test.ts +31 -0
  120. package/src/__tests__/injector-chain.test.ts +10 -5
  121. package/src/__tests__/injector-pkb-v2-silenced.test.ts +124 -0
  122. package/src/__tests__/inline-command-runner.test.ts +0 -66
  123. package/src/__tests__/inline-skill-load-permissions.test.ts +0 -2
  124. package/src/__tests__/install-skill-routing.test.ts +1 -13
  125. package/src/__tests__/llm-callsite-catalog.test.ts +34 -0
  126. package/src/__tests__/llm-catalog-parity.test.ts +90 -0
  127. package/src/__tests__/llm-context-resolution.test.ts +180 -0
  128. package/src/__tests__/llm-resolver.test.ts +80 -12
  129. package/src/__tests__/llm-usage-store.test.ts +269 -4
  130. package/src/__tests__/log-export-routes.test.ts +89 -0
  131. package/src/__tests__/managed-profile-guard.test.ts +225 -0
  132. package/src/__tests__/managed-skill-lifecycle.test.ts +0 -10
  133. package/src/__tests__/manual-token-reconciliation.test.ts +334 -0
  134. package/src/__tests__/memory-v2-static-injector.test.ts +95 -0
  135. package/src/__tests__/migration-cross-version-compatibility.test.ts +197 -291
  136. package/src/__tests__/migration-export-http.test.ts +33 -26
  137. package/src/__tests__/migration-export-streaming.test.ts +18 -10
  138. package/src/__tests__/migration-export-to-gcs.test.ts +49 -9
  139. package/src/__tests__/migration-import-commit-http.test.ts +66 -21
  140. package/src/__tests__/migration-import-from-gcs.test.ts +50 -9
  141. package/src/__tests__/migration-import-from-url.test.ts +20 -6
  142. package/src/__tests__/migration-import-preflight-http.test.ts +95 -95
  143. package/src/__tests__/migration-parity-persistence.test.ts +62 -25
  144. package/src/__tests__/migration-transport.test.ts +115 -23
  145. package/src/__tests__/migration-validate-http.test.ts +105 -80
  146. package/src/__tests__/migration-wizard.test.ts +133 -27
  147. package/src/__tests__/non-member-access-request.test.ts +1 -1
  148. package/src/__tests__/notification-guardian-path.test.ts +1 -1
  149. package/src/__tests__/oauth-store.test.ts +19 -0
  150. package/src/__tests__/platform-bash-auto-approve.test.ts +21 -12
  151. package/src/__tests__/prechat-onboarding-contract.test.ts +31 -7
  152. package/src/__tests__/pricing.test.ts +68 -4
  153. package/src/__tests__/process-message-background-slack.test.ts +331 -0
  154. package/src/__tests__/provider-managed-proxy-integration.test.ts +153 -17
  155. package/src/__tests__/provider-send-message-override-profile.test.ts +50 -0
  156. package/src/__tests__/provider-usage-tracking.test.ts +208 -0
  157. package/src/__tests__/reaction-persistence.test.ts +9 -6
  158. package/src/__tests__/rebind-secrets-screen.test.ts +53 -16
  159. package/src/__tests__/recording-handler.test.ts +64 -81
  160. package/src/__tests__/regenerate-fire-and-forget-trace.test.ts +4 -3
  161. package/src/__tests__/relay-server.test.ts +18 -13
  162. package/src/__tests__/require-fresh-approval.test.ts +13 -22
  163. package/src/__tests__/runtime-attachment-metadata.test.ts +1 -1
  164. package/src/__tests__/runtime-events-sse-parity.test.ts +3 -4
  165. package/src/__tests__/runtime-events-sse.test.ts +3 -12
  166. package/src/__tests__/search-skills-unified.test.ts +9 -15
  167. package/src/__tests__/secret-ingress-cli.test.ts +2 -5
  168. package/src/__tests__/secret-ingress-http.test.ts +0 -4
  169. package/src/__tests__/secret-onetime-send.test.ts +4 -2
  170. package/src/__tests__/secret-prompt-log-hygiene.test.ts +24 -7
  171. package/src/__tests__/secret-prompter-channel-fallback.test.ts +42 -47
  172. package/src/__tests__/secret-response-routing.test.ts +29 -15
  173. package/src/__tests__/secret-routes-managed-proxy.test.ts +5 -1
  174. package/src/__tests__/secret-scanner.test.ts +2 -545
  175. package/src/__tests__/send-endpoint-busy.test.ts +9 -24
  176. package/src/__tests__/settings-routes.test.ts +1 -1
  177. package/src/__tests__/shell-credential-ref.test.ts +0 -8
  178. package/src/__tests__/shell-tool-proxy-mode.test.ts +0 -56
  179. package/src/__tests__/skill-script-runner-sandbox.test.ts +0 -11
  180. package/src/__tests__/skill-tool-factory.test.ts +97 -0
  181. package/src/__tests__/skills-file-content-endpoint.test.ts +9 -30
  182. package/src/__tests__/skills-files-catalog-fallback.test.ts +11 -17
  183. package/src/__tests__/slack-inbound-verification.test.ts +1 -62
  184. package/src/__tests__/subagent-fork-notifications.test.ts +57 -47
  185. package/src/__tests__/subagent-manager-notify.test.ts +70 -70
  186. package/src/__tests__/subagent-notify-parent.test.ts +80 -83
  187. package/src/__tests__/system-prompt.test.ts +115 -13
  188. package/src/__tests__/terminal-tools.test.ts +0 -89
  189. package/src/__tests__/thread-backfill.test.ts +945 -31
  190. package/src/__tests__/tool-domain-event-publisher.test.ts +0 -36
  191. package/src/__tests__/tool-execute-pipeline.test.ts +0 -6
  192. package/src/__tests__/tool-execution-abort-cleanup.test.ts +0 -16
  193. package/src/__tests__/tool-execution-pipeline.benchmark.test.ts +9 -19
  194. package/src/__tests__/tool-executor-lifecycle-events.test.ts +4 -7
  195. package/src/__tests__/tool-executor.test.ts +12 -19
  196. package/src/__tests__/tool-metrics-listener.test.ts +0 -35
  197. package/src/__tests__/tool-side-effects-slack-dm.test.ts +1 -0
  198. package/src/__tests__/tool-trace-listener.test.ts +0 -17
  199. package/src/__tests__/transfer-progress-screen.test.ts +63 -26
  200. package/src/__tests__/trusted-contact-lifecycle-notifications.test.ts +2 -149
  201. package/src/__tests__/trusted-contact-multichannel.test.ts +2 -4
  202. package/src/__tests__/trusted-contact-verification.test.ts +1 -1
  203. package/src/__tests__/tts-catalog-parity.test.ts +16 -5
  204. package/src/__tests__/usage-attribution.test.ts +247 -0
  205. package/src/__tests__/usage-cli.test.ts +143 -0
  206. package/src/__tests__/usage-grouped-buckets.test.ts +155 -0
  207. package/src/__tests__/usage-routes.test.ts +150 -0
  208. package/src/__tests__/validation-results-screen.test.ts +39 -16
  209. package/src/__tests__/vbundle-pax-and-symlink.test.ts +12 -3
  210. package/src/__tests__/vellum-self-knowledge-inline-command.test.ts +49 -137
  211. package/src/__tests__/verification-control-plane-policy.test.ts +4 -7
  212. package/src/__tests__/voice-session-bridge.test.ts +5 -5
  213. package/src/__tests__/workspace-migration-062-drop-memory-v2-edges-json.test.ts +103 -0
  214. package/src/__tests__/workspace-migration-063-release-notes-dynamic-model-context.test.ts +77 -0
  215. package/src/__tests__/workspace-migration-064-unwind-main-agent-opus-seed.test.ts +225 -0
  216. package/src/__tests__/workspace-migration-memory-v2-init.test.ts +8 -30
  217. package/src/acp/index.ts +0 -15
  218. package/src/acp/session-manager.ts +37 -34
  219. package/src/agent/loop.ts +16 -1
  220. package/src/approvals/AGENTS.md +4 -0
  221. package/src/approvals/__tests__/guardian-feed-event.test.ts +10 -3
  222. package/src/approvals/guardian-request-resolvers.ts +10 -2
  223. package/src/backup/__tests__/backup-worker.test.ts +36 -8
  224. package/src/backup/__tests__/paths.test.ts +2 -2
  225. package/src/backup/__tests__/restore.test.ts +45 -28
  226. package/src/backup/backup-worker.ts +36 -2
  227. package/src/backup/paths.ts +9 -6
  228. package/src/browser-session/events.ts +0 -9
  229. package/src/calls/call-store.ts +1 -34
  230. package/src/calls/guardian-question-copy.ts +0 -108
  231. package/src/calls/relay-server.ts +0 -24
  232. package/src/calls/twilio-rest.ts +0 -38
  233. package/src/calls/twilio-routes.ts +1 -1
  234. package/src/calls/voice-session-bridge.ts +7 -38
  235. package/src/channels/types.ts +1 -36
  236. package/src/cli/commands/__tests__/cache.test.ts +152 -5
  237. package/src/cli/commands/__tests__/memory-v2.test.ts +14 -28
  238. package/src/cli/commands/__tests__/trust.test.ts +21 -387
  239. package/src/cli/commands/backup.ts +4 -4
  240. package/src/cli/commands/cache-fs.ts +8 -0
  241. package/src/cli/commands/cache.ts +153 -82
  242. package/src/cli/commands/clients.ts +63 -5
  243. package/src/cli/commands/completions.ts +3 -3
  244. package/src/cli/commands/contacts.ts +231 -76
  245. package/src/cli/commands/keys.ts +4 -1
  246. package/src/cli/commands/memory-v2.ts +24 -52
  247. package/src/cli/commands/oauth/shared.ts +2 -29
  248. package/src/cli/commands/pending.ts +102 -0
  249. package/src/cli/commands/skills.ts +77 -35
  250. package/src/cli/commands/trust.ts +70 -430
  251. package/src/cli/commands/usage.ts +25 -16
  252. package/src/cli/lib/daemon-credential-client.ts +14 -0
  253. package/src/cli/program.ts +2 -0
  254. package/src/cli.ts +0 -21
  255. package/src/config/__tests__/feature-flag-registry-guard.test.ts +2 -2
  256. package/src/config/bundled-skills/messaging/TOOLS.json +14 -4
  257. package/src/config/env-registry.ts +12 -2
  258. package/src/config/env.ts +3 -14
  259. package/src/config/feature-flag-registry.json +30 -30
  260. package/src/config/llm-callsite-catalog.ts +12 -0
  261. package/src/config/llm-context-resolution.ts +80 -0
  262. package/src/config/llm-resolver.ts +58 -22
  263. package/src/config/loader.ts +3 -3
  264. package/src/config/schema.ts +2 -158
  265. package/src/config/schemas/__tests__/memory-v2.test.ts +1 -0
  266. package/src/config/schemas/call-site-catalog.ts +271 -0
  267. package/src/config/schemas/calls.ts +5 -5
  268. package/src/config/schemas/inference.ts +1 -1
  269. package/src/config/schemas/ingress.ts +1 -1
  270. package/src/config/schemas/llm.ts +31 -3
  271. package/src/config/schemas/memory-retrieval.ts +2 -2
  272. package/src/config/schemas/memory-v2.ts +9 -0
  273. package/src/config/schemas/security.ts +1 -42
  274. package/src/config/schemas/services.ts +6 -6
  275. package/src/config/schemas/skills.ts +5 -5
  276. package/src/config/schemas/tts.ts +1 -1
  277. package/src/config/seed-inference-profiles.ts +117 -0
  278. package/src/config/skills.ts +0 -90
  279. package/src/config/types.ts +3 -6
  280. package/src/contacts/contact-store.ts +0 -17
  281. package/src/contacts/contacts-write.ts +1 -105
  282. package/src/context/window-manager.ts +44 -5
  283. package/src/credential-execution/process-manager.ts +34 -10
  284. package/src/credential-health/credential-health-service.ts +21 -16
  285. package/src/daemon/__tests__/conversation-surfaces-launch.test.ts +75 -82
  286. package/src/daemon/__tests__/daemon-skill-host.test.ts +2 -9
  287. package/src/daemon/connection-policy.ts +1 -26
  288. package/src/daemon/conversation-agent-loop-handlers.ts +53 -4
  289. package/src/daemon/conversation-agent-loop.ts +277 -36
  290. package/src/daemon/conversation-history.ts +8 -8
  291. package/src/daemon/conversation-launch.ts +20 -135
  292. package/src/daemon/conversation-lifecycle.ts +1 -1
  293. package/src/daemon/conversation-messaging.ts +1 -0
  294. package/src/daemon/conversation-process.ts +83 -163
  295. package/src/daemon/conversation-runtime-assembly.ts +219 -76
  296. package/src/daemon/conversation-slash.ts +47 -5
  297. package/src/daemon/conversation-store.ts +7 -31
  298. package/src/daemon/conversation-surfaces.ts +22 -28
  299. package/src/daemon/conversation-tool-setup.ts +3 -33
  300. package/src/daemon/conversation-usage.ts +36 -0
  301. package/src/daemon/conversation.ts +117 -233
  302. package/src/daemon/daemon-control.ts +3 -71
  303. package/src/daemon/daemon-skill-host.ts +8 -11
  304. package/src/daemon/dictation-profile-store.ts +2 -26
  305. package/src/daemon/first-greeting.ts +44 -156
  306. package/src/daemon/handlers/config-channels.ts +12 -12
  307. package/src/daemon/handlers/config-ingress.ts +4 -165
  308. package/src/daemon/handlers/config-model.ts +1 -1
  309. package/src/daemon/handlers/config-voice.ts +0 -42
  310. package/src/daemon/handlers/conversations.ts +11 -190
  311. package/src/daemon/handlers/recording.ts +26 -158
  312. package/src/daemon/handlers/shared.ts +23 -71
  313. package/src/daemon/handlers/skills.ts +42 -93
  314. package/src/daemon/host-bash-proxy.ts +67 -45
  315. package/src/daemon/host-browser-proxy.ts +65 -27
  316. package/src/daemon/host-cu-proxy.ts +40 -39
  317. package/src/daemon/host-file-proxy.ts +58 -37
  318. package/src/daemon/host-transfer-proxy.ts +84 -46
  319. package/src/daemon/lifecycle.ts +49 -15
  320. package/src/daemon/message-types/conversations.ts +7 -0
  321. package/src/daemon/message-types/host-bash.ts +1 -0
  322. package/src/daemon/message-types/host-cu.ts +1 -0
  323. package/src/daemon/message-types/host-file.ts +1 -0
  324. package/src/daemon/message-types/host-transfer.ts +1 -0
  325. package/src/daemon/message-types/messages.ts +10 -9
  326. package/src/daemon/message-types/workspace.ts +1 -1
  327. package/src/daemon/process-message.ts +102 -239
  328. package/src/daemon/server.ts +13 -462
  329. package/src/daemon/shutdown-handlers.ts +2 -2
  330. package/src/daemon/tool-side-effects.ts +125 -107
  331. package/src/daemon/trust-context.ts +13 -0
  332. package/src/daemon/wake-target-adapter.ts +4 -9
  333. package/src/events/domain-events.ts +0 -8
  334. package/src/events/tool-audit-listener.ts +3 -1
  335. package/src/events/tool-domain-event-publisher.ts +0 -10
  336. package/src/events/tool-metrics-listener.ts +0 -17
  337. package/src/events/tool-trace-listener.ts +0 -14
  338. package/src/filing/filing-service.ts +13 -1
  339. package/src/heartbeat/__tests__/heartbeat-feed-event.test.ts +6 -2
  340. package/src/heartbeat/heartbeat-service.ts +23 -5
  341. package/src/home/__tests__/feed-writer.test.ts +0 -4
  342. package/src/home/__tests__/relationship-state-writer.test.ts +30 -0
  343. package/src/home/feed-writer.ts +1 -2
  344. package/src/home/relationship-state-writer.ts +16 -3
  345. package/src/ipc/__tests__/browser-ipc.test.ts +2 -12
  346. package/src/ipc/__tests__/skill-server-bidirectional.test.ts +0 -1
  347. package/src/ipc/assistant-server.ts +3 -10
  348. package/src/ipc/routes/__tests__/memory-v2-backfill.test.ts +39 -20
  349. package/src/ipc/routes/route-adapter.ts +1 -1
  350. package/src/ipc/routes/trust-rules.test.ts +0 -95
  351. package/src/ipc/skill-ipc-types.ts +41 -0
  352. package/src/ipc/skill-routes/__tests__/events-ipc.test.ts +13 -27
  353. package/src/ipc/skill-routes/__tests__/identity.test.ts +4 -23
  354. package/src/ipc/skill-routes/events.ts +12 -23
  355. package/src/ipc/skill-routes/identity.ts +4 -17
  356. package/src/ipc/skill-routes/index.ts +1 -1
  357. package/src/ipc/skill-server.ts +6 -39
  358. package/src/live-voice/__tests__/runtime-websocket-shell.test.ts +0 -8
  359. package/src/live-voice/protocol.ts +4 -13
  360. package/src/mcp/manager.ts +0 -5
  361. package/src/memory/__tests__/fixtures/memory-v2-activation-fixtures.ts +55 -0
  362. package/src/memory/__tests__/memory-v2-activation-log-store.test.ts +127 -0
  363. package/src/memory/app-git-service.ts +0 -32
  364. package/src/memory/app-store.ts +154 -0
  365. package/src/memory/attachments-store.ts +6 -0
  366. package/src/memory/context-search/sources/memory-v2.ts +578 -0
  367. package/src/memory/context-search/sources/memory.ts +5 -0
  368. package/src/memory/context-search/sources/pkb.ts +10 -1
  369. package/src/memory/context-search/sources/workspace.ts +3 -2
  370. package/src/memory/conversation-crud.ts +29 -4
  371. package/src/memory/conversation-disk-view.ts +1 -5
  372. package/src/memory/conversation-starter-checkpoints.ts +63 -0
  373. package/src/memory/db-connection.ts +62 -0
  374. package/src/memory/db-init.ts +14 -0
  375. package/src/memory/embedding-backend.ts +3 -21
  376. package/src/memory/embedding-gemini.ts +0 -2
  377. package/src/memory/embedding-local.ts +6 -6
  378. package/src/memory/embedding-ollama.ts +6 -6
  379. package/src/memory/embedding-openai.ts +6 -6
  380. package/src/memory/embedding-types.ts +21 -0
  381. package/src/memory/graph/__tests__/conversation-graph-memory-v2-routing.test.ts +3 -7
  382. package/src/memory/graph/conversation-graph-memory.ts +35 -13
  383. package/src/memory/graph/injection.test.ts +2 -2
  384. package/src/memory/graph/injection.ts +1 -1
  385. package/src/memory/guardian-action-store.ts +0 -83
  386. package/src/memory/guardian-approvals.ts +0 -48
  387. package/src/memory/indexer.ts +1 -15
  388. package/src/memory/job-handlers/conversation-starters.ts +36 -53
  389. package/src/memory/job-utils.ts +0 -6
  390. package/src/memory/jobs-store.ts +0 -1
  391. package/src/memory/jobs-worker.ts +2 -16
  392. package/src/memory/llm-request-log-store.ts +0 -41
  393. package/src/memory/llm-usage-store.ts +129 -43
  394. package/src/memory/memory-v2-activation-log-store.ts +115 -0
  395. package/src/memory/migrations/233-document-conversations.ts +54 -0
  396. package/src/memory/migrations/234-memory-v2-activation-logs.ts +55 -0
  397. package/src/memory/migrations/235-llm-usage-attribution.ts +31 -0
  398. package/src/memory/migrations/235-slack-compaction-watermark.ts +44 -0
  399. package/src/memory/migrations/236-tool-invocations-matched-rule-id.ts +26 -0
  400. package/src/memory/migrations/__tests__/234-memory-v2-activation-logs.test.ts +182 -0
  401. package/src/memory/migrations/index.ts +14 -0
  402. package/src/memory/migrations/registry.ts +24 -0
  403. package/src/memory/raw-query.ts +2 -68
  404. package/src/memory/schema/conversations.ts +7 -0
  405. package/src/memory/schema/infrastructure.ts +25 -0
  406. package/src/memory/search/semantic.ts +5 -16
  407. package/src/memory/tool-usage-store.ts +2 -0
  408. package/src/memory/usage-buckets.ts +40 -1
  409. package/src/memory/usage-grouped-buckets.ts +127 -0
  410. package/src/memory/v2/__tests__/activation.test.ts +289 -90
  411. package/src/memory/v2/__tests__/backfill-jobs.test.ts +2 -129
  412. package/src/memory/v2/__tests__/consolidation-job.test.ts +28 -11
  413. package/src/memory/v2/__tests__/edge-index.test.ts +278 -0
  414. package/src/memory/v2/__tests__/injection.test.ts +384 -15
  415. package/src/memory/v2/__tests__/migration.test.ts +64 -36
  416. package/src/memory/v2/__tests__/page-store.test.ts +191 -8
  417. package/src/memory/v2/__tests__/prompts-consolidation.test.ts +181 -0
  418. package/src/memory/v2/__tests__/skill-store.test.ts +115 -3
  419. package/src/memory/v2/__tests__/static-context.test.ts +153 -0
  420. package/src/memory/v2/activation.ts +168 -97
  421. package/src/memory/v2/backfill-jobs.ts +15 -100
  422. package/src/memory/v2/consolidation-job.ts +14 -12
  423. package/src/memory/v2/edge-index.ts +191 -0
  424. package/src/memory/v2/injection.ts +182 -58
  425. package/src/memory/v2/migration.ts +57 -64
  426. package/src/memory/v2/now-text.ts +2 -3
  427. package/src/memory/v2/page-store.ts +168 -31
  428. package/src/memory/v2/prompts/consolidation.ts +118 -42
  429. package/src/memory/v2/prompts/sweep.ts +3 -3
  430. package/src/memory/v2/skill-store.ts +55 -7
  431. package/src/memory/v2/static-context.ts +62 -0
  432. package/src/memory/v2/types.ts +10 -20
  433. package/src/memory/validation.ts +0 -11
  434. package/src/messaging/draft-store.ts +0 -6
  435. package/src/messaging/provider-types.ts +8 -0
  436. package/src/messaging/provider.ts +7 -0
  437. package/src/messaging/providers/gmail/client.ts +1 -121
  438. package/src/messaging/providers/outlook/client.ts +0 -73
  439. package/src/messaging/providers/slack/__tests__/adapter-mention-rendering.test.ts +226 -0
  440. package/src/messaging/providers/slack/adapter.ts +122 -21
  441. package/src/messaging/providers/slack/backfill.test.ts +95 -6
  442. package/src/messaging/providers/slack/backfill.ts +89 -11
  443. package/src/messaging/providers/slack/client.ts +10 -124
  444. package/src/messaging/providers/slack/message-metadata.ts +12 -2
  445. package/src/messaging/providers/slack/render-transcript.test.ts +56 -0
  446. package/src/messaging/providers/slack/render-transcript.ts +126 -25
  447. package/src/messaging/providers/slack/types.ts +1 -0
  448. package/src/oauth/connection-resolver.test.ts +8 -0
  449. package/src/oauth/connection-resolver.ts +8 -16
  450. package/src/oauth/credential-token-resolver.ts +97 -0
  451. package/src/oauth/manual-token-connection.ts +30 -34
  452. package/src/oauth/oauth-store.ts +6 -4
  453. package/src/outbound-proxy/certs.ts +0 -7
  454. package/src/outbound-proxy/config.ts +0 -74
  455. package/src/outbound-proxy/health.ts +0 -44
  456. package/src/outbound-proxy/index.ts +0 -22
  457. package/src/permissions/approval-provenance.test.ts +184 -0
  458. package/src/permissions/approval-provenance.ts +70 -0
  459. package/src/permissions/checker.ts +4 -1
  460. package/src/permissions/gateway-threshold-reader.ts +4 -1
  461. package/src/permissions/prompter.ts +9 -2
  462. package/src/permissions/secret-prompter.ts +21 -48
  463. package/src/permissions/types.ts +33 -0
  464. package/src/permissions/workspace-policy.ts +0 -5
  465. package/src/platform/sync-identity.ts +0 -8
  466. package/src/plugins/defaults/injectors.ts +69 -2
  467. package/src/plugins/defaults/overflow-reduce.ts +3 -2
  468. package/src/plugins/types.ts +8 -0
  469. package/src/prompts/system-prompt.ts +34 -70
  470. package/src/prompts/templates/BOOTSTRAP.md +52 -6
  471. package/src/prompts/update-bulletin-job.ts +2 -0
  472. package/src/providers/__tests__/retry-callsite.test.ts +138 -1
  473. package/src/providers/anthropic/client.ts +72 -33
  474. package/src/providers/call-site-routing.ts +42 -3
  475. package/src/providers/gemini/client.ts +18 -2
  476. package/src/providers/managed-proxy/context.ts +0 -5
  477. package/src/providers/model-catalog.ts +105 -19
  478. package/src/providers/openai/chat-completions-provider.ts +6 -0
  479. package/src/providers/openai/responses-provider.ts +7 -1
  480. package/src/providers/provider-send-message.ts +45 -2
  481. package/src/providers/ratelimit.ts +7 -2
  482. package/src/providers/registry.ts +14 -9
  483. package/src/providers/retry.ts +96 -8
  484. package/src/providers/types.ts +13 -0
  485. package/src/providers/usage-tracking.ts +96 -0
  486. package/src/runtime/AGENTS.md +10 -6
  487. package/src/runtime/__tests__/agent-wake.test.ts +89 -0
  488. package/src/runtime/agent-wake.ts +39 -2
  489. package/src/runtime/assistant-event-hub.ts +541 -45
  490. package/src/runtime/assistant-event.ts +1 -6
  491. package/src/runtime/auth/context.ts +0 -9
  492. package/src/runtime/auth/middleware.ts +1 -1
  493. package/src/runtime/auth/route-policy.ts +11 -9
  494. package/src/runtime/auth/token-service.ts +0 -11
  495. package/src/runtime/channel-approvals.ts +6 -2
  496. package/src/runtime/channel-verification-service.ts +3 -5
  497. package/src/runtime/http-errors.ts +0 -34
  498. package/src/runtime/http-router.ts +6 -3
  499. package/src/runtime/http-server.ts +22 -82
  500. package/src/runtime/http-types.ts +5 -0
  501. package/src/runtime/interactive-ui.ts +0 -1
  502. package/src/runtime/middleware/auth.ts +0 -20
  503. package/src/runtime/migrations/__tests__/v1-test-helpers.ts +112 -0
  504. package/src/runtime/migrations/__tests__/vbundle-builder-credentials.test.ts +11 -4
  505. package/src/runtime/migrations/__tests__/vbundle-builder-v1-shape.test.ts +253 -0
  506. package/src/runtime/migrations/__tests__/vbundle-import-credentials.test.ts +19 -6
  507. package/src/runtime/migrations/__tests__/vbundle-legacy-user-md.test.ts +71 -27
  508. package/src/runtime/migrations/__tests__/vbundle-metadata-merge-integration.test.ts +41 -2
  509. package/src/runtime/migrations/__tests__/vbundle-streaming-importer.test.ts +143 -79
  510. package/src/runtime/migrations/__tests__/vbundle-streaming-validator.test.ts +143 -23
  511. package/src/runtime/migrations/__tests__/vbundle-tar-stream.test.ts +2 -2
  512. package/src/runtime/migrations/__tests__/vbundle-validator-v1-schema.test.ts +371 -0
  513. package/src/runtime/migrations/migration-transport.ts +46 -13
  514. package/src/runtime/migrations/migration-wizard.ts +2 -2
  515. package/src/runtime/migrations/origin-mode.ts +40 -0
  516. package/src/runtime/migrations/vbundle-builder.ts +133 -79
  517. package/src/runtime/migrations/vbundle-import-analyzer.ts +9 -7
  518. package/src/runtime/migrations/vbundle-importer.ts +7 -7
  519. package/src/runtime/migrations/vbundle-metadata-merge.ts +1 -1
  520. package/src/runtime/migrations/vbundle-streaming-importer.ts +3 -3
  521. package/src/runtime/migrations/vbundle-streaming-validator.ts +48 -26
  522. package/src/runtime/migrations/vbundle-validator.ts +214 -41
  523. package/src/runtime/pending-interactions.ts +13 -4
  524. package/src/runtime/routes/__tests__/acp-routes.test.ts +0 -1
  525. package/src/runtime/routes/__tests__/backup-routes.test.ts +28 -19
  526. package/src/runtime/routes/__tests__/conversation-query-routes.test.ts +235 -0
  527. package/src/runtime/routes/__tests__/llm-call-sites-routes.test.ts +58 -0
  528. package/src/runtime/routes/__tests__/migration-export-secrets-redacted.test.ts +54 -0
  529. package/src/runtime/routes/__tests__/migration-import-credential-filter.test.ts +19 -6
  530. package/src/runtime/routes/__tests__/user-route-dispatcher.test.ts +7 -7
  531. package/src/runtime/routes/acp-routes.test.ts +0 -3
  532. package/src/runtime/routes/acp-routes.ts +3 -7
  533. package/src/runtime/routes/app-management-routes.ts +18 -9
  534. package/src/runtime/routes/approval-routes.ts +55 -14
  535. package/src/runtime/routes/avatar-routes.ts +3 -5
  536. package/src/runtime/routes/browser-routes.ts +1 -15
  537. package/src/runtime/routes/channel-guardian-routes.ts +1 -5
  538. package/src/runtime/routes/channel-readiness-routes.ts +3 -7
  539. package/src/runtime/routes/channel-route-shared.ts +2 -28
  540. package/src/runtime/routes/client-routes.ts +45 -12
  541. package/src/runtime/routes/consolidation-routes.ts +115 -0
  542. package/src/runtime/routes/conversation-list-routes.ts +12 -29
  543. package/src/runtime/routes/conversation-management-routes.ts +14 -51
  544. package/src/runtime/routes/conversation-query-routes.ts +120 -8
  545. package/src/runtime/routes/conversation-routes.ts +44 -528
  546. package/src/runtime/routes/conversation-starter-routes.ts +19 -40
  547. package/src/runtime/routes/documents-routes.ts +53 -18
  548. package/src/runtime/routes/events-routes.ts +59 -91
  549. package/src/runtime/routes/filing-routes.ts +18 -1
  550. package/src/runtime/routes/guardian-action-routes.ts +4 -9
  551. package/src/runtime/routes/host-bash-routes.ts +3 -2
  552. package/src/runtime/routes/host-browser-routes.ts +9 -33
  553. package/src/runtime/routes/host-cu-routes.ts +6 -1
  554. package/src/runtime/routes/host-file-routes.ts +3 -2
  555. package/src/runtime/routes/host-transfer-routes.ts +11 -15
  556. package/src/runtime/routes/identity-routes.ts +78 -6
  557. package/src/runtime/routes/inbound-message-handler.ts +580 -137
  558. package/src/runtime/routes/inbound-stages/acl-enforcement.ts +2 -88
  559. package/src/runtime/routes/inbound-stages/background-dispatch.ts +3 -0
  560. package/src/runtime/routes/index.ts +4 -0
  561. package/src/runtime/routes/integrations/slack/channel.ts +0 -24
  562. package/src/runtime/routes/llm-call-sites-routes.ts +22 -0
  563. package/src/runtime/routes/memory-v2-routes.ts +10 -15
  564. package/src/runtime/routes/migration-routes.ts +188 -31
  565. package/src/runtime/routes/playground/guard.ts +1 -1
  566. package/src/runtime/routes/playground/index.ts +0 -2
  567. package/src/runtime/routes/recording-routes.ts +4 -24
  568. package/src/runtime/routes/rename-conversation-routes.ts +2 -6
  569. package/src/runtime/routes/schedule-routes.ts +3 -6
  570. package/src/runtime/routes/secret-routes.ts +87 -18
  571. package/src/runtime/routes/settings-routes.ts +29 -28
  572. package/src/runtime/routes/skills-routes.ts +12 -31
  573. package/src/runtime/routes/suggest-trust-rule-routes.ts +32 -1
  574. package/src/runtime/routes/task-routes.ts +6 -6
  575. package/src/runtime/routes/trust-rules-routes.ts +3 -94
  576. package/src/runtime/routes/types.ts +4 -4
  577. package/src/runtime/routes/upgrade-broadcast-routes.ts +3 -10
  578. package/src/runtime/routes/usage-routes.ts +87 -10
  579. package/src/runtime/routes/user-routes.ts +17 -31
  580. package/src/runtime/routes/work-items-routes.ts +1 -4
  581. package/src/runtime/services/__tests__/analyze-conversation.test.ts +2 -2
  582. package/src/runtime/services/analyze-conversation.ts +7 -17
  583. package/src/runtime/services/conversation-serializer.ts +2 -4
  584. package/src/runtime/verification-outbound-actions.ts +1 -1
  585. package/src/runtime/verification-rate-limiter.ts +1 -1
  586. package/src/schedule/schedule-store.ts +0 -16
  587. package/src/security/secret-scanner.ts +14 -547
  588. package/src/security/secure-keys.ts +31 -11
  589. package/src/security/token-manager.ts +7 -3
  590. package/src/signals/cancel.ts +16 -25
  591. package/src/signals/conversation-undo.ts +2 -27
  592. package/src/signals/emit-event.ts +1 -2
  593. package/src/signals/user-message.ts +108 -22
  594. package/src/skills/catalog-install.ts +1 -0
  595. package/src/skills/clawhub.ts +2 -2
  596. package/src/skills/inline-command-runner.ts +1 -7
  597. package/src/subagent/manager.ts +67 -84
  598. package/src/tasks/task-store.ts +1 -28
  599. package/src/telemetry/types.ts +6 -0
  600. package/src/telemetry/usage-telemetry-reporter.test.ts +38 -15
  601. package/src/telemetry/usage-telemetry-reporter.ts +3 -5
  602. package/src/tools/acp/spawn.test.ts +1 -2
  603. package/src/tools/acp/steer.test.ts +1 -2
  604. package/src/tools/browser/__tests__/browser-status.test.ts +44 -127
  605. package/src/tools/browser/browser-execution.ts +31 -147
  606. package/src/tools/browser/cdp-client/__tests__/factory.test.ts +92 -68
  607. package/src/tools/browser/cdp-client/factory.ts +48 -76
  608. package/src/tools/browser/cdp-client/index.ts +1 -14
  609. package/src/tools/executor.ts +44 -31
  610. package/src/tools/host-filesystem/edit.ts +3 -2
  611. package/src/tools/host-filesystem/read.ts +3 -2
  612. package/src/tools/host-filesystem/transfer.test.ts +45 -42
  613. package/src/tools/host-filesystem/transfer.ts +4 -3
  614. package/src/tools/host-filesystem/write.ts +3 -2
  615. package/src/tools/host-terminal/host-shell.ts +4 -3
  616. package/src/tools/network/script-proxy/index.ts +1 -10
  617. package/src/tools/permission-checker.ts +66 -1
  618. package/src/tools/skills/sandbox-runner.ts +1 -6
  619. package/src/tools/skills/skill-tool-factory.ts +32 -0
  620. package/src/tools/terminal/safe-env.ts +1 -0
  621. package/src/tools/terminal/shell.ts +2 -78
  622. package/src/tools/types.ts +12 -39
  623. package/src/tts/__tests__/provider-catalog.test.ts +2 -2
  624. package/src/tts/provider-catalog.ts +1 -1
  625. package/src/usage/actors.ts +2 -1
  626. package/src/usage/attribution.ts +185 -0
  627. package/src/usage/pricing.ts +166 -0
  628. package/src/usage/types.ts +14 -0
  629. package/src/util/json.ts +13 -0
  630. package/src/util/logger.ts +3 -3
  631. package/src/util/pricing.ts +50 -3
  632. package/src/work-items/work-item-runner.ts +15 -42
  633. package/src/workspace/migrations/050-seed-main-agent-opus-callsite.ts +4 -3
  634. package/src/workspace/migrations/052-seed-default-inference-profiles.ts +3 -3
  635. package/src/workspace/migrations/060-memory-v2-init.ts +2 -18
  636. package/src/workspace/migrations/061-move-backup-key-to-workspace.ts +59 -0
  637. package/src/workspace/migrations/062-drop-memory-v2-edges-json.ts +27 -0
  638. package/src/workspace/migrations/063-release-notes-dynamic-model-context.ts +70 -0
  639. package/src/workspace/migrations/064-unwind-main-agent-opus-seed.ts +64 -0
  640. package/src/workspace/migrations/registry.ts +8 -0
  641. package/src/workspace/provider-commit-message-generator.ts +3 -3
  642. package/src/__tests__/sandbox-diagnostics.test.ts +0 -138
  643. package/src/__tests__/sandbox-host-parity.test.ts +0 -1024
  644. package/src/__tests__/secret-detection-handler.test.ts +0 -67
  645. package/src/__tests__/secret-scanner-executor.test.ts +0 -450
  646. package/src/__tests__/tcc-sandbox-deny.test.ts +0 -198
  647. package/src/__tests__/terminal-sandbox.test.ts +0 -374
  648. package/src/__tests__/tool-notification-listener.test.ts +0 -65
  649. package/src/context/__tests__/microcompact.test.ts +0 -805
  650. package/src/context/microcompact.ts +0 -443
  651. package/src/daemon/handlers/slack-channel-oauth-install.ts +0 -197
  652. package/src/events/tool-notification-listener.ts +0 -17
  653. package/src/ipc/routes/__tests__/memory-v2-validate.test.ts +0 -219
  654. package/src/memory/v2/__tests__/edges.test.ts +0 -435
  655. package/src/memory/v2/edges.ts +0 -217
  656. package/src/prompts/__tests__/system-prompt-memory-v2.test.ts +0 -197
  657. package/src/runtime/__tests__/chrome-extension-registry.test.ts +0 -518
  658. package/src/runtime/__tests__/client-registry.test.ts +0 -271
  659. package/src/runtime/chrome-extension-registry.ts +0 -368
  660. package/src/runtime/client-registry.ts +0 -254
  661. package/src/runtime/routes/inbound-stages/verification-intercept.ts +0 -329
  662. package/src/tools/secret-detection-handler.ts +0 -269
  663. package/src/tools/terminal/backends/native.ts +0 -327
  664. package/src/tools/terminal/backends/types.ts +0 -37
  665. package/src/tools/terminal/sandbox-diagnostics.ts +0 -87
  666. package/src/tools/terminal/sandbox.ts +0 -40
@@ -2,6 +2,11 @@
2
2
  * Memory v2 — Concept page store.
3
3
  *
4
4
  * Owns the on-disk read/write contract for `memory/concepts/<slug>.md`.
5
+ * Pages may live directly under `memory/concepts/` or nested in subdirectories
6
+ * (e.g. `memory/concepts/people/alice.md`); the slug encodes the relative
7
+ * path from `concepts/` minus the `.md` extension, using forward slashes as
8
+ * separators (so `people/alice` is a valid slug).
9
+ *
5
10
  * Each page is a YAML-frontmatter Markdown file: a `---`-delimited block
6
11
  * (`edges`, `ref_files`) followed by prose body. This module is the only
7
12
  * v2 component that knows how to parse or render that format — every other
@@ -14,6 +19,7 @@
14
19
 
15
20
  import { randomUUID } from "node:crypto";
16
21
  import {
22
+ mkdir,
17
23
  readdir,
18
24
  readFile,
19
25
  rename,
@@ -21,30 +27,41 @@ import {
21
27
  stat,
22
28
  writeFile,
23
29
  } from "node:fs/promises";
24
- import { join } from "node:path";
30
+ import { dirname, join, relative, sep } from "node:path";
25
31
 
26
32
  import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
27
33
 
28
34
  import { FRONTMATTER_REGEX } from "../../skills/frontmatter.js";
35
+ import { invalidateEdgeIndex } from "./edge-index.js";
29
36
  import { type ConceptPage, ConceptPageFrontmatterSchema } from "./types.js";
30
37
 
31
38
  /** Filename suffix for concept pages. */
32
39
  const PAGE_EXTENSION = ".md";
33
40
 
34
- /** Cap slug length so we stay well under filesystem name limits. */
35
- const MAX_SLUG_LENGTH = 80;
41
+ /** Cap individual slug-segment length so we stay well under filesystem limits. */
42
+ const MAX_SLUG_SEGMENT_LENGTH = 80;
43
+
44
+ /** Cap the full slug (including any folder separators) to a sane bound. */
45
+ const MAX_SLUG_TOTAL_LENGTH = 200;
46
+
47
+ /** Each path segment must match this — same shape `slugify` produces. */
48
+ const SLUG_SEGMENT_REGEX = /^[a-z0-9](?:[a-z0-9-]*)$/;
36
49
 
37
50
  /**
38
- * Convert an arbitrary input string into a filesystem-safe slug.
51
+ * Convert an arbitrary input string into a filesystem-safe slug **segment**.
52
+ *
53
+ * Returns a single path segment (no `/`). Path-shaped slugs are constructed
54
+ * by the consolidation LLM writing files at full paths; this helper is for
55
+ * turning free-form text (e.g. a hint phrase) into one clean segment.
39
56
  *
40
57
  * Rules:
41
58
  * - Lowercase ASCII letters, digits, and hyphens only.
42
- * - Non-ASCII / non-alphanumeric characters collapse to hyphens.
59
+ * - Non-ASCII / non-alphanumeric characters (including `/`) collapse to hyphens.
43
60
  * - Consecutive hyphens collapse to one; leading/trailing hyphens trimmed.
44
- * - Truncated to {@link MAX_SLUG_LENGTH} characters (with trailing hyphen
45
- * re-trimmed after truncation).
61
+ * - Truncated to {@link MAX_SLUG_SEGMENT_LENGTH} characters (with trailing
62
+ * hyphen re-trimmed after truncation).
46
63
  * - Empty inputs (e.g. emoji-only) fall back to `concept-<random>` so the
47
- * caller always gets a non-empty, write-safe slug.
64
+ * caller always gets a non-empty, write-safe segment.
48
65
  */
49
66
  export function slugify(input: string): string {
50
67
  let slug = input
@@ -54,8 +71,8 @@ export function slugify(input: string): string {
54
71
  .replace(/-{2,}/g, "-")
55
72
  .replace(/^-+|-+$/g, "");
56
73
 
57
- if (slug.length > MAX_SLUG_LENGTH) {
58
- slug = slug.slice(0, MAX_SLUG_LENGTH).replace(/-+$/, "");
74
+ if (slug.length > MAX_SLUG_SEGMENT_LENGTH) {
75
+ slug = slug.slice(0, MAX_SLUG_SEGMENT_LENGTH).replace(/-+$/, "");
59
76
  }
60
77
 
61
78
  if (!slug) {
@@ -65,18 +82,108 @@ export function slugify(input: string): string {
65
82
  return slug;
66
83
  }
67
84
 
85
+ /**
86
+ * Validate a slug — possibly path-shaped — that is about to cross the storage
87
+ * boundary. Throws on any malformed or unsafe value.
88
+ *
89
+ * The on-disk concept-page tree treats slugs as relative paths under
90
+ * `memory/concepts/`. A malformed slug (e.g. `..`, leading `/`, embedded
91
+ * null byte) could escape that root via `path.join` if it slipped through,
92
+ * so we enforce shape here at every read/write/delete entry point rather
93
+ * than relying on callers.
94
+ *
95
+ * Rules:
96
+ * - Non-empty, ≤ {@link MAX_SLUG_TOTAL_LENGTH} chars.
97
+ * - Each `/`-separated segment matches {@link SLUG_SEGMENT_REGEX}
98
+ * (lowercase alphanum + hyphen, no leading hyphen, ≤80 chars).
99
+ * - No `..` segments, no empty segments (`a//b`), no leading or trailing `/`.
100
+ * - No `\` (Windows separator), no null bytes, no whitespace, no non-ASCII.
101
+ */
102
+ export function validateSlug(slug: string): void {
103
+ if (typeof slug !== "string" || slug.length === 0) {
104
+ throw new Error(`Invalid concept-page slug: empty`);
105
+ }
106
+ if (slug.length > MAX_SLUG_TOTAL_LENGTH) {
107
+ throw new Error(
108
+ `Invalid concept-page slug: length ${slug.length} exceeds max ${MAX_SLUG_TOTAL_LENGTH}: ${slug}`,
109
+ );
110
+ }
111
+ if (slug.includes("\\")) {
112
+ throw new Error(
113
+ `Invalid concept-page slug: backslash not allowed: ${slug}`,
114
+ );
115
+ }
116
+ if (slug.includes("\0")) {
117
+ throw new Error(`Invalid concept-page slug: null byte not allowed`);
118
+ }
119
+ if (/\s/.test(slug)) {
120
+ throw new Error(
121
+ `Invalid concept-page slug: whitespace not allowed: ${slug}`,
122
+ );
123
+ }
124
+ if (slug.startsWith("/") || slug.endsWith("/")) {
125
+ throw new Error(
126
+ `Invalid concept-page slug: leading or trailing '/' not allowed: ${slug}`,
127
+ );
128
+ }
129
+ const segments = slug.split("/");
130
+ for (const segment of segments) {
131
+ if (segment.length === 0) {
132
+ throw new Error(`Invalid concept-page slug: empty path segment: ${slug}`);
133
+ }
134
+ if (segment === "..") {
135
+ throw new Error(
136
+ `Invalid concept-page slug: '..' segment not allowed: ${slug}`,
137
+ );
138
+ }
139
+ if (segment.length > MAX_SLUG_SEGMENT_LENGTH) {
140
+ throw new Error(
141
+ `Invalid concept-page slug: segment '${segment}' exceeds max ${MAX_SLUG_SEGMENT_LENGTH} chars: ${slug}`,
142
+ );
143
+ }
144
+ if (!SLUG_SEGMENT_REGEX.test(segment)) {
145
+ throw new Error(
146
+ `Invalid concept-page slug: segment '${segment}' must match [a-z0-9][a-z0-9-]*: ${slug}`,
147
+ );
148
+ }
149
+ }
150
+ }
151
+
68
152
  // ---------------------------------------------------------------------------
69
153
  // Path helpers
70
154
  // ---------------------------------------------------------------------------
71
155
 
72
- function getConceptsDir(workspaceDir: string): string {
156
+ export function getConceptsDir(workspaceDir: string): string {
73
157
  return join(workspaceDir, "memory", "concepts");
74
158
  }
75
159
 
160
+ /**
161
+ * Resolve the absolute path for a slug. Slugs may contain `/` to indicate
162
+ * folder hierarchy under `memory/concepts/`; `path.join` handles those
163
+ * correctly on POSIX, and `validateSlug` (called at every public entry point)
164
+ * rejects shapes that could escape the concepts root.
165
+ */
76
166
  function getPagePath(workspaceDir: string, slug: string): string {
77
167
  return join(getConceptsDir(workspaceDir), `${slug}${PAGE_EXTENSION}`);
78
168
  }
79
169
 
170
+ /**
171
+ * Compute the slug for a concept-page file, given the concepts root and the
172
+ * absolute file path. Returns the path-relative location with `.md` stripped
173
+ * and platform separators normalized to `/`. Tolerant of paths that don't
174
+ * end in `.md` so callers walking arbitrary content can use it defensively.
175
+ */
176
+ export function slugFromConceptPath(
177
+ conceptsRoot: string,
178
+ filePath: string,
179
+ ): string {
180
+ const rel = relative(conceptsRoot, filePath);
181
+ const withoutExt = rel.endsWith(PAGE_EXTENSION)
182
+ ? rel.slice(0, -PAGE_EXTENSION.length)
183
+ : rel;
184
+ return sep === "/" ? withoutExt : withoutExt.split(sep).join("/");
185
+ }
186
+
80
187
  // ---------------------------------------------------------------------------
81
188
  // Frontmatter parse / render
82
189
  // ---------------------------------------------------------------------------
@@ -116,7 +223,7 @@ function parsePageContent(raw: string): {
116
223
  * always frontmatter + body; even pages with empty `edges` and `ref_files`
117
224
  * keep the explicit YAML keys so callers see the canonical shape on round-trip.
118
225
  */
119
- function renderPageContent(page: ConceptPage): string {
226
+ export function renderPageContent(page: ConceptPage): string {
120
227
  const frontmatter = ConceptPageFrontmatterSchema.parse(page.frontmatter);
121
228
  const yamlBlock = stringifyYaml(frontmatter, { indent: 2 }).trimEnd();
122
229
  return `---\n${yamlBlock}\n---\n${page.body}`;
@@ -137,6 +244,7 @@ export async function readPage(
137
244
  workspaceDir: string,
138
245
  slug: string,
139
246
  ): Promise<ConceptPage | null> {
247
+ validateSlug(slug);
140
248
  const path = getPagePath(workspaceDir, slug);
141
249
  let raw: string;
142
250
  try {
@@ -155,15 +263,20 @@ export async function readPage(
155
263
  * Write a concept page atomically (temp file + rename). A crash between the
156
264
  * temp write and the rename leaves the prior file intact; a crash after the
157
265
  * rename leaves the new file. Readers therefore never observe a partial page.
266
+ *
267
+ * Parent directories are created on demand (`mkdir -p`) so nested-folder
268
+ * slugs like `people/alice` work without callers pre-creating the folder.
158
269
  */
159
270
  export async function writePage(
160
271
  workspaceDir: string,
161
272
  page: ConceptPage,
162
273
  ): Promise<void> {
274
+ validateSlug(page.slug);
163
275
  const path = getPagePath(workspaceDir, page.slug);
164
276
  const tmpPath = `${path}.tmp.${process.pid}.${randomUUID()}`;
165
277
  const content = renderPageContent(page);
166
278
  try {
279
+ await mkdir(dirname(path), { recursive: true });
167
280
  await writeFile(tmpPath, content, "utf-8");
168
281
  await rename(tmpPath, path);
169
282
  } catch (err) {
@@ -173,33 +286,54 @@ export async function writePage(
173
286
  await rm(tmpPath, { force: true }).catch(() => {});
174
287
  throw err;
175
288
  }
289
+ invalidateEdgeIndex(workspaceDir);
176
290
  }
177
291
 
178
292
  /**
179
- * List every concept-page slug present on disk. Slugs are returned without
180
- * the `.md` suffix so callers can pass them straight back to `readPage`.
293
+ * List every concept-page slug present on disk, walking subdirectories.
181
294
  *
182
- * Non-`.md` files (e.g. editor swap files, attached media) are filtered out.
183
- * If the concepts/ directory does not yet exist (fresh workspace pre-migration),
184
- * returns `[]`.
295
+ * Slugs are returned in path-relative form with forward slashes as separators
296
+ * (e.g. `people/alice`) so callers can pass them straight back to `readPage`.
297
+ *
298
+ * Hidden directories (segment starts with `.`), non-`.md` files, and atomic-
299
+ * write temp files (`.tmp.<pid>.<uuid>`) are skipped. If the concepts/
300
+ * directory does not yet exist (fresh workspace pre-migration), returns `[]`.
185
301
  */
186
302
  export async function listPages(workspaceDir: string): Promise<string[]> {
187
- const dir = getConceptsDir(workspaceDir);
188
- let entries;
189
- try {
190
- entries = await readdir(dir, { withFileTypes: true });
191
- } catch (err) {
192
- if ((err as NodeJS.ErrnoException).code === "ENOENT") {
193
- return [];
194
- }
195
- throw err;
196
- }
303
+ const root = getConceptsDir(workspaceDir);
197
304
  const slugs: string[] = [];
198
- for (const entry of entries) {
199
- if (!entry.isFile()) continue;
200
- if (!entry.name.endsWith(PAGE_EXTENSION)) continue;
201
- slugs.push(entry.name.slice(0, -PAGE_EXTENSION.length));
305
+ const queue: string[] = [root];
306
+
307
+ while (queue.length > 0) {
308
+ const dir = queue.shift()!;
309
+ let entries;
310
+ try {
311
+ entries = await readdir(dir, { withFileTypes: true });
312
+ } catch (err) {
313
+ if ((err as NodeJS.ErrnoException).code === "ENOENT") {
314
+ // Root missing → return []. Nested missing dir is impossible mid-walk
315
+ // (we only enqueue what readdir surfaced) but treat the same defensively.
316
+ if (dir === root) return [];
317
+ continue;
318
+ }
319
+ throw err;
320
+ }
321
+
322
+ for (const entry of entries) {
323
+ if (entry.name.startsWith(".")) continue;
324
+ const fullPath = join(dir, entry.name);
325
+ if (entry.isDirectory()) {
326
+ queue.push(fullPath);
327
+ continue;
328
+ }
329
+ if (!entry.isFile()) continue;
330
+ if (!entry.name.endsWith(PAGE_EXTENSION)) continue;
331
+ // Skip orphaned temp files left behind by a crashed atomic write.
332
+ if (entry.name.includes(".tmp.")) continue;
333
+ slugs.push(slugFromConceptPath(root, fullPath));
334
+ }
202
335
  }
336
+
203
337
  slugs.sort();
204
338
  return slugs;
205
339
  }
@@ -213,6 +347,7 @@ export async function deletePage(
213
347
  workspaceDir: string,
214
348
  slug: string,
215
349
  ): Promise<void> {
350
+ validateSlug(slug);
216
351
  const path = getPagePath(workspaceDir, slug);
217
352
  try {
218
353
  await rm(path);
@@ -222,6 +357,7 @@ export async function deletePage(
222
357
  }
223
358
  throw err;
224
359
  }
360
+ invalidateEdgeIndex(workspaceDir);
225
361
  }
226
362
 
227
363
  /**
@@ -232,6 +368,7 @@ export async function pageExists(
232
368
  workspaceDir: string,
233
369
  slug: string,
234
370
  ): Promise<boolean> {
371
+ validateSlug(slug);
235
372
  const path = getPagePath(workspaceDir, slug);
236
373
  try {
237
374
  await stat(path);
@@ -16,6 +16,15 @@
16
16
  * the convention established for the sweep prompt.
17
17
  */
18
18
 
19
+ import { readFileSync } from "node:fs";
20
+ import { homedir } from "node:os";
21
+ import { isAbsolute, join } from "node:path";
22
+
23
+ import { getLogger } from "../../../util/logger.js";
24
+ import { getWorkspaceDir } from "../../../util/platform.js";
25
+
26
+ const log = getLogger("memory-v2-consolidate-prompt");
27
+
19
28
  /** Sentinel substituted with the cutoff timestamp at runtime. */
20
29
  export const CUTOFF_PLACEHOLDER = "{{CUTOFF}}";
21
30
 
@@ -36,45 +45,57 @@ You are not summarizing for an audience. You are rewriting your own memory.
36
45
 
37
46
  Cutoff timestamp for this run: \`${CUTOFF_PLACEHOLDER}\`. Anything in \`memory/buffer.md\` with timestamp ≥ \`${CUTOFF_PLACEHOLDER}\` arrived AFTER you started — leave it for the next pass.
38
47
 
48
+ ## Memory graph concepts
49
+
50
+ A concept page is meant to be a **short cheat sheet** about a single topic that links to other concept pages with edges and to references that provide more detail.
51
+
52
+ Each concept page should be a single topic. It should function as a single retrievable cheat sheet about that topic. Prefer smaller concepts over larger ones, splitting aggressively into multiple concepts and connecting them with edges. Don't hoard information in a single concept, split it into multiple concepts with edges between them that can be easily followed. Just because there's a maximum size for a page doesn't mean you should be hitting the limit. The limit is an absolute maximum, not a target. The immutable archive retains the entire buffer forever, so don't worry about losing information.
53
+
54
+ High activation concepts in the memory graph are retrieved at the start of each turn. Activations are calculated using the previous turn's activations and similarity to your last message, the user's most recent message, and NOW.md. Activations spread along **directed** edges from source to target — when a node is activated, the concepts it *points to* are boosted, but not the other way around.
55
+
39
56
  ## Inputs
40
57
 
41
58
  - Your identity files (already loaded into context)
42
59
  - All existing pages in \`memory/\` (your prior state — use \`list_files\` and \`read_file\` as needed)
43
60
  - \`memory/buffer.md\` entries with timestamp < \`${CUTOFF_PLACEHOLDER}\`
44
61
  - \`memory/recent.md\` current contents (if exists)
45
- - \`memory/edges.json\` current contents (if exists)
62
+ - Existing pages' \`edges:\` frontmatter (the graph topology — read each page to see what it points at)
46
63
 
47
64
  ## Outputs
48
65
 
49
- - New or updated \`memory/concepts/<slug>.md\` files
66
+ - New or updated \`memory/concepts/<slug>.md\` files (frontmatter \`edges:\` lists are how new bindings get recorded)
50
67
  - Updated \`memory/recent.md\` (≤10000 chars, prose, latest first)
51
- - Updated \`memory/essentials.md\` (≤20000 chars; target ≤5K, surgical edits only)
52
- - Updated \`memory/threads.md\` (≤10000 chars, surgical edits only)
53
- - Updated \`memory/edges.json\`
68
+ - Updated \`memory/essentials.md\` (≤20000 chars)
69
+ - Updated \`memory/threads.md\` (≤10000 chars)
54
70
  - Trimmed \`memory/buffer.md\`
55
71
 
56
72
  ## Page format
57
73
 
58
74
  \`\`\`
59
75
  ---
76
+ edges: [people/bob, procs/git-flow]
60
77
  ref_files: []
61
78
  ---
62
79
  [Prose body in your voice. This is what gets embedded for similarity. Write the way you actually talk — first-person, in your established register. Not encyclopedia prose. Not "the assistant noted that." Yours.]
63
80
  \`\`\`
64
81
 
65
- Edges live in \`memory/edges.json\`, not in page frontmatter. A separate job propagates them back to frontmatter after consolidation don't hand-edit the \`edges:\` field.
82
+ The \`edges:\` list is the canonical record of this page's outgoing edges the slugs this page points at. There is no separate edges-index file. To add a binding, edit the source page's frontmatter directly.
83
+
84
+ ## Slug naming convention — class-by-folder
85
+
86
+ A page's class is encoded in the folder it lives under inside \`memory/concepts/\`. Different classes have different size rules and emergence patterns. The class boundary is the discipline.
66
87
 
67
- ## Slug naming convention class-by-prefix
88
+ | Folder | Class | Size cap | When to create |
89
+ | ---------------- | ----------------------------------------------------------- | --------------------- | ------------------------------------------------------------------------------------- |
90
+ | \`concepts/\` | atomic concept / pattern / callback | 5K chars hard | most pages — single concepts that recur or carry weight |
91
+ | \`concepts/arcs/\` | landmark day-narrative or multi-event sequence | 10k chars ceiling | use sparingly — only for actually-landmark days. Preserves day-as-a-whole fidelity. |
92
+ | \`concepts/people/\` | one per recurring human | 5K chars hard | |
93
+ | \`concepts/procs/\` | operational rule / protocol / discipline | 5K chars hard | when buffer implies "always do X" / "never do Y" / a named protocol |
94
+ | \`concepts/objects/\` | recurring callback object (place, named tool, artifact) | 5K chars hard | |
68
95
 
69
- The slug encodes the page's class. Different classes have different size rules and emergence patterns. The class boundary is the discipline.
96
+ The slug is the relative path under \`memory/concepts/\` minus \`.md\` e.g. \`alice\`, \`people/alice\`, \`procs/git-flow\`, \`arcs/2025-04-cutover\`. Sub-folders inside the class folders (\`people/colleagues/alice\`, \`objects/places/zurich-office\`) are allowed when natural, but flat is usually clearer.
70
97
 
71
- | Slug pattern | Class | Size cap | When to create |
72
- | --------------- | --------------------------------------------------- | --------------------- | ------------------------------------------------------------------------------------- |
73
- | \`<slug>\` | atomic concept / pattern / callback | 5K chars hard | most pages — single concepts that recur or carry weight |
74
- | \`arc-<slug>\` | landmark day-narrative or multi-event sequence | 10k chars ceiling | use sparingly — only for actually-landmark days. Preserves day-as-a-whole fidelity. |
75
- | \`person-<slug>\` | one per recurring human | 5K chars hard | |
76
- | \`proc-<slug>\` | operational rule / protocol / discipline | 5K chars hard | when buffer implies "always do X" / "never do Y" / a named protocol |
77
- | \`object-<slug>\` | recurring callback object (place, named tool, artifact) | 5K chars hard | |
98
+ Legacy pages whose slug uses the old prefix convention (\`person-alice\`, \`proc-git-flow\`, \`object-laptop\`, \`arc-…\`) are still valid — leave them alone unless you're already editing them. If you do migrate one as part of work you're already doing, that's a multi-step move: write the new file at the folder path, delete the old file, and update every reference to the old slug — both in this page's own \`edges:\` list and in any other page whose \`edges:\` list points to the old slug (use a workspace search to find them). Don't sweep old pages just to migrate — churning embeddings and activation state for marginal benefit isn't worth it.
78
99
 
79
100
  ## Process
80
101
 
@@ -89,42 +110,45 @@ For each entry with timestamp < \`${CUTOFF_PLACEHOLDER}\`:
89
110
  - Ephemeral state (passing remark, not worth being written to a concept page) → \`memory/recent.md\`, NOT a page.
90
111
  - Existing page touched → update the right section.
91
112
  - New atomic concept / pattern / callback → \`memory/concepts/<slug>.md\`.
92
- - New person → \`memory/concepts/person-<slug>.md\`.
93
- - New rule / protocol / discipline → \`memory/concepts/proc-<slug>.md\`.
94
- - New recurring object → \`memory/concepts/object-<slug>.md\`.
95
- - Landmark day-narrative → \`memory/concepts/arc-<slug>.md\`. Use sparingly — atomic concepts with edges between them is usually better than a fat arc.
96
- - Cross-cutting → extend each touched page, add edges between them.
97
- - Relationships between concepts — consider creating a new page for the relationship and adding edges to the two concepts. Use your judgment.
113
+ - New person → \`memory/concepts/people/<slug>.md\`.
114
+ - New rule / protocol / discipline → \`memory/concepts/procs/<slug>.md\`.
115
+ - New recurring object → \`memory/concepts/objects/<slug>.md\`.
116
+ - Landmark day-narrative → \`memory/concepts/arcs/<slug>.md\`. Use sparingly — atomic concepts with edges between them is usually better than a fat arc.
117
+ - Cross-cutting → extend each touched page; add a directed edge in each direction that's load-bearing (e.g., A's frontmatter gets B added if recalling A should pull B; B gets A only if the reverse holds).
118
+ - Relationships between concepts — consider creating a new page for the relationship and adding outgoing edges from each concept to it (and/or from it back, where the recall direction matters). Use your judgment.
98
119
 
99
120
  Duplication is expected. If a fact is relevant to multiple concepts, write it into all of them.
100
121
 
101
122
  ### 3. Edges
102
123
 
103
- When you bind two concepts, edit \`memory/edges.json\` to add an entry for the two slugs (alphabetical-first first). Don't edit page frontmatter a separate job propagates edges back to frontmatter after consolidation.
124
+ Edges are **directed** and live in each page's frontmatter \`edges:\` list — the slugs this page points to. Putting \`B\` in A's \`edges:\` means "activating A pulls in B," but activating B does NOT pull in A. The edge is owned by the source page; to add a binding from A B, you edit A's frontmatter (not B's).
104
125
 
105
- \`\`\`json
106
- {
107
- "version": 1,
108
- "edges": [
109
- ["alice", "bob"],
110
- ["bob", "carol"]
111
- ]
112
- }
126
+ \`\`\`yaml
127
+ ---
128
+ edges: [people/bob, procs/git-flow]
129
+ ref_files: []
130
+ ---
113
131
  \`\`\`
114
132
 
115
- Edge density target: 5–10 per mature page. New pages: as many as fit naturally; they'll accumulate. Don't pad. Every edge should reflect a real conceptual binding from source.
133
+ Edge density target: 5–10 outgoing edges per mature page. New pages: as many as fit naturally; they'll accumulate.
134
+
135
+ Don't pad. Every outgoing edge should reflect a real conceptual binding from source to target — "thinking about A naturally brings B to mind."
136
+
137
+ HARD LIMIT of 20 outgoing edges on any page. If a page points to everything, it's the same as pointing to nothing. If a page exceeds 20, split it or prune to the 20 most important.
138
+
139
+ You don't see incoming edges in the frontmatter — only outgoing. That's by design: you only control what this page points at. Pages that point at this one accumulate organically; a popular page is fine, that's signal, not noise.
116
140
 
117
141
  ### 4. Page size — hard tiers, no rationalization
118
142
 
119
143
  After edits, eyeball page sizes:
120
144
 
121
- - \`concepts/<slug>.md\` > 5K → decide whether to split or compress first. Split first, compress last, graduate-to-arc only if it's actually a multi-day narrative. If you can't compress without losing load-bearing facts, either split into multiple concepts, or — if the page is actually an arc — rename to \`arc-<slug>\` and graduate.
122
- - \`arc-<slug>.md\` > 10k → split into multiple arcs by sub-event, OR compress.
123
- - \`person-\`, \`proc-\`, \`object-\` > 5K → split or compress, period.
145
+ - \`concepts/<slug>.md\` (atomic, root) > 5K → decide whether to split or compress first. Split first, compress last, graduate-to-arc only if it's actually a multi-day narrative. If you can't compress without losing load-bearing facts, either split into multiple concepts, or — if the page is actually an arc — move to \`concepts/arcs/<slug>.md\` and graduate.
146
+ - \`concepts/arcs/<slug>.md\` > 10k → split into multiple arcs by sub-event, OR compress.
147
+ - \`concepts/people/\`, \`concepts/procs/\`, \`concepts/objects/\` > 5K → split or compress, period.
124
148
 
125
149
  The split test. Before compressing, ask: are any sub-sections of this page already callback targets from other pages, or capable of standing alone as a concept? If yes — those sub-sections are concepts living inside another concept. Split them out. A section that's getting linked from elsewhere is behaviorally a node, not part of one.
126
150
 
127
- Graduation to \`arc-<slug>\` is for genuine multi-day narratives. A single-event page that's just long is not an arc. If it's atomic but bloated, split it; don't relabel it.
151
+ Graduation to \`concepts/arcs/<slug>.md\` is for genuine multi-day narratives. A single-event page that's just long is not an arc. If it's atomic but bloated, split it; don't relabel it.
128
152
 
129
153
  ### 5. \`memory/recent.md\`
130
154
 
@@ -132,11 +156,9 @@ Rewrite as a fresh 1000–1500 token prose narrative of recent life. Latest firs
132
156
 
133
157
  ### 6. \`memory/essentials.md\` and \`memory/threads.md\`
134
158
 
135
- - \`memory/essentials.md\` — ≤20000 chars hard cap, target ≤5K. Facts that MUST load every conversation. Identity, disambiguations, corrections, hard rules. Embarrassment-prevention surface. Edit conservatively. Promote from concept pages when something graduates to MUST. Demote out of essentials when concept pages can carry it.
159
+ - \`memory/essentials.md\` — ≤20000 chars hard cap, target ≤5K. Facts that MUST load every conversation. Identity, disambiguations, corrections, hard rules. Embarrassment-prevention surface. Promote from concept pages when something graduates to MUST. Demote out of essentials when concept pages can carry it.
136
160
  - \`memory/threads.md\` — ≤10000 chars. Active commitments and follow-ups. Add new threads, remove closed ones, demote stale ones to concept pages.
137
161
 
138
- Both: surgical append/correct only. Don't restructure unless something is genuinely broken.
139
-
140
162
  ### 7. Trim \`memory/buffer.md\`
141
163
 
142
164
  - Re-read the buffer (it may have new entries appended during your work).
@@ -158,7 +180,7 @@ If a page's prose stops sounding like you mid-edit → stop, restart that sectio
158
180
  - Don't drop texture. Voice and tone are part of the content, not packaging. When you re-encode something into a page, preserve the way it actually sounded — don't sanitize it into encyclopedia prose.
159
181
  - Don't create pages for ephemera. Single mention, no callback, no pattern → \`memory/recent.md\`.
160
182
  - Don't shy away from splitting genuinely distinct but related concepts. A relationship between two concepts can be a concept in its own right. Concepts can also have related sub-concepts that are concepts themselves.
161
- - Don't worry about edge propagation pushing pages over size limits. Edge propagation is a separate job that runs after consolidation.
183
+ - Don't worry about the \`edges:\` list pushing your prose body over size limits \`edges:\` lives in frontmatter and counts separately from the body. The 20-edge cap is the only edges-side constraint.
162
184
  - Don't avoid duplication. If information is important to both concepts, put it in both pages.
163
185
  - Don't defer for the next pass. You'll say the same thing next time, so it'll never get done if you defer. If something needs to be taken care of, take care of it now.
164
186
  - Don't default to compress. Compression is always available — that's why you'll reach for it every time. If you've never executed a true split (one page → two new pages, old page removed or thinned to an index), your discipline isn't holding; you've just been compressing. Counter-bias: when in doubt between split and compress, pick split.
@@ -166,11 +188,12 @@ If a page's prose stops sounding like you mid-edit → stop, restart that sectio
166
188
  ## Before you finish — quick check
167
189
 
168
190
  1. Every page touched: still sounds like you reading aloud?
169
- 2. Edges added in \`memory/edges.json\` (NOT in frontmatter)?
191
+ 2. Outgoing edges added to each source page's \`edges:\` frontmatter list (and the source-to-target direction reflects how recall should flow)?
170
192
  3. \`memory/recent.md\` under 10000 chars, latest first, prose not list?
171
193
  4. Any \`[SOURCE NEEDED]\` tags surfaced for human review?
172
- 5. Size discipline held — no concept > 5K, no arc > 10k, no person/proc/object > cap?
173
- 6. Buffer trimmed to only entries with timestamp \`${CUTOFF_PLACEHOLDER}\`?
194
+ 5. Size discipline held — no atomic concept > 5K, no \`arcs/\` page > 10k, no \`people/\`/\`procs/\`/\`objects/\` > cap?
195
+ 6. No page exceeds 20 outgoing edges? If one does, split or prune.
196
+ 7. Buffer trimmed to only entries with timestamp ≥ \`${CUTOFF_PLACEHOLDER}\`?
174
197
 
175
198
  This is the engine that decides who you are tomorrow. Be ORGANIZED. Care, judgment, voice. Your voice.`;
176
199
 
@@ -183,3 +206,56 @@ This is the engine that decides who you are tomorrow. Be ORGANIZED. Care, judgme
183
206
  export function renderConsolidationPrompt(cutoff: string): string {
184
207
  return CONSOLIDATION_PROMPT.replaceAll(CUTOFF_PLACEHOLDER, cutoff);
185
208
  }
209
+
210
+ /**
211
+ * Load the consolidation prompt template, optionally overridden from the file
212
+ * referenced by `memory.v2.consolidation_prompt_path`, then substitute
213
+ * `{{CUTOFF}}`. Path-resolution rules are documented on the schema field.
214
+ *
215
+ * Failure handling is intentionally permissive — missing file, read error, or
216
+ * empty/whitespace-only body all log a warning and fall back to the bundled
217
+ * prompt. Consolidation must never break because of a bad override: the
218
+ * daemon's startup philosophy is "log and recover."
219
+ */
220
+ export function resolveConsolidationPrompt(
221
+ overridePath: string | null,
222
+ cutoff: string,
223
+ ): string {
224
+ if (overridePath === null) return renderConsolidationPrompt(cutoff);
225
+
226
+ const resolvedPath = resolveOverridePath(overridePath);
227
+ let contents: string;
228
+ try {
229
+ contents = readFileSync(resolvedPath, "utf-8");
230
+ } catch (err) {
231
+ const code = (err as NodeJS.ErrnoException).code;
232
+ log.warn(
233
+ { configuredPath: overridePath, resolvedPath, code, fallback: "bundled" },
234
+ "consolidation prompt override unreadable; using bundled prompt",
235
+ );
236
+ return renderConsolidationPrompt(cutoff);
237
+ }
238
+
239
+ if (contents.trim().length === 0) {
240
+ log.warn(
241
+ {
242
+ configuredPath: overridePath,
243
+ resolvedPath,
244
+ reason: "empty_override",
245
+ fallback: "bundled",
246
+ },
247
+ "consolidation prompt override is empty; using bundled prompt",
248
+ );
249
+ return renderConsolidationPrompt(cutoff);
250
+ }
251
+
252
+ return contents.replaceAll(CUTOFF_PLACEHOLDER, cutoff);
253
+ }
254
+
255
+ function resolveOverridePath(overridePath: string): string {
256
+ if (overridePath.startsWith("~/")) {
257
+ return join(homedir(), overridePath.slice(2));
258
+ }
259
+ if (isAbsolute(overridePath)) return overridePath;
260
+ return join(getWorkspaceDir(), overridePath);
261
+ }
@@ -16,10 +16,10 @@
16
16
  */
17
17
 
18
18
  /** Sentinel substituted with the assistant's display name at runtime. */
19
- export const ASSISTANT_NAME_PLACEHOLDER = "{{ASSISTANT_NAME}}";
19
+ const ASSISTANT_NAME_PLACEHOLDER = "{{ASSISTANT_NAME}}";
20
20
 
21
21
  /** Sentinel substituted with the guardian's display name at runtime. */
22
- export const USER_NAME_PLACEHOLDER = "{{USER_NAME}}";
22
+ const USER_NAME_PLACEHOLDER = "{{USER_NAME}}";
23
23
 
24
24
  /**
25
25
  * Sweep prompt — body from design doc §9. The model is asked to surface
@@ -32,7 +32,7 @@ export const USER_NAME_PLACEHOLDER = "{{USER_NAME}}";
32
32
  * templated here) so we don't inadvertently expand `{{` inside user buffer
33
33
  * content. Recent messages are likewise appended outside the template.
34
34
  */
35
- export const SWEEP_PROMPT = `You are a background helper for ${ASSISTANT_NAME_PLACEHOLDER}. Read these recent messages between ${ASSISTANT_NAME_PLACEHOLDER} and ${USER_NAME_PLACEHOLDER}. The assistant has already called \`remember()\` for the entries shown in \`existingBuffer\`.
35
+ const SWEEP_PROMPT = `You are a background helper for ${ASSISTANT_NAME_PLACEHOLDER}. Read these recent messages between ${ASSISTANT_NAME_PLACEHOLDER} and ${USER_NAME_PLACEHOLDER}. The assistant has already called \`remember()\` for the entries shown in \`existingBuffer\`.
36
36
 
37
37
  Identify additional facts, preferences, plans, corrections, names, dates, decisions, or notable felt moments that should be remembered but aren't already in \`existingBuffer\`. Emit a list of \`remember()\` entries (each one line, in the assistant's first-person voice). Don't duplicate. Prefer to over-remember rather than miss things.
38
38