@vellumai/assistant 0.5.5 → 0.5.6

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 (102) hide show
  1. package/Dockerfile +3 -4
  2. package/package.json +1 -1
  3. package/src/__tests__/actor-token-service.test.ts +113 -0
  4. package/src/__tests__/config-schema.test.ts +2 -2
  5. package/src/__tests__/context-window-manager.test.ts +78 -0
  6. package/src/__tests__/conversation-title-service.test.ts +30 -1
  7. package/src/__tests__/docker-signing-key-bootstrap.test.ts +207 -0
  8. package/src/__tests__/memory-regressions.test.ts +8 -30
  9. package/src/__tests__/require-fresh-approval.test.ts +4 -0
  10. package/src/__tests__/tool-executor-lifecycle-events.test.ts +4 -0
  11. package/src/__tests__/tool-executor.test.ts +4 -0
  12. package/src/cli/commands/conversations.ts +0 -18
  13. package/src/config/env.ts +8 -2
  14. package/src/config/feature-flag-registry.json +0 -8
  15. package/src/config/schema.ts +0 -12
  16. package/src/config/schemas/memory.ts +0 -4
  17. package/src/config/schemas/platform.ts +1 -1
  18. package/src/config/schemas/security.ts +4 -0
  19. package/src/context/window-manager.ts +53 -2
  20. package/src/daemon/config-watcher.ts +1 -4
  21. package/src/daemon/conversation-agent-loop.ts +0 -60
  22. package/src/daemon/conversation-memory.ts +0 -117
  23. package/src/daemon/conversation-runtime-assembly.ts +0 -2
  24. package/src/daemon/handlers/conversations.ts +0 -11
  25. package/src/daemon/lifecycle.ts +3 -46
  26. package/src/followups/followup-store.ts +5 -2
  27. package/src/memory/conversation-crud.ts +0 -236
  28. package/src/memory/conversation-title-service.ts +26 -10
  29. package/src/memory/db-init.ts +5 -13
  30. package/src/memory/indexer.ts +15 -106
  31. package/src/memory/job-handlers/embedding.ts +0 -79
  32. package/src/memory/job-utils.ts +1 -1
  33. package/src/memory/jobs-store.ts +0 -8
  34. package/src/memory/jobs-worker.ts +0 -20
  35. package/src/memory/migrations/189-drop-simplified-memory.ts +42 -0
  36. package/src/memory/migrations/index.ts +1 -3
  37. package/src/memory/qdrant-client.ts +4 -6
  38. package/src/memory/schema/conversations.ts +0 -3
  39. package/src/memory/schema/index.ts +0 -2
  40. package/src/messaging/draft-store.ts +2 -2
  41. package/src/permissions/defaults.ts +3 -3
  42. package/src/permissions/trust-client.ts +2 -13
  43. package/src/permissions/trust-store.ts +8 -3
  44. package/src/runtime/auth/route-policy.ts +14 -0
  45. package/src/runtime/auth/token-service.ts +133 -0
  46. package/src/runtime/http-server.ts +2 -0
  47. package/src/runtime/routes/conversation-management-routes.ts +0 -36
  48. package/src/runtime/routes/conversation-query-routes.ts +44 -2
  49. package/src/runtime/routes/conversation-routes.ts +2 -1
  50. package/src/runtime/routes/memory-item-routes.test.ts +221 -3
  51. package/src/runtime/routes/memory-item-routes.ts +124 -2
  52. package/src/runtime/routes/upgrade-broadcast-routes.ts +151 -0
  53. package/src/schedule/schedule-store.ts +0 -21
  54. package/src/skills/inline-command-render.ts +5 -1
  55. package/src/skills/inline-command-runner.ts +30 -2
  56. package/src/tools/memory/handlers.ts +1 -129
  57. package/src/tools/permission-checker.ts +18 -0
  58. package/src/tools/skills/load.ts +9 -2
  59. package/src/util/platform.ts +5 -5
  60. package/src/util/xml.ts +8 -0
  61. package/src/workspace/heartbeat-service.ts +5 -24
  62. package/src/__tests__/archive-recall.test.ts +0 -560
  63. package/src/__tests__/conversation-memory-dirty-tail.test.ts +0 -150
  64. package/src/__tests__/conversation-switch-memory-reduction.test.ts +0 -474
  65. package/src/__tests__/db-memory-archive-migration.test.ts +0 -372
  66. package/src/__tests__/db-memory-brief-state-migration.test.ts +0 -213
  67. package/src/__tests__/db-memory-reducer-checkpoints.test.ts +0 -273
  68. package/src/__tests__/memory-brief-open-loops.test.ts +0 -530
  69. package/src/__tests__/memory-brief-time.test.ts +0 -285
  70. package/src/__tests__/memory-brief-wrapper.test.ts +0 -311
  71. package/src/__tests__/memory-chunk-archive.test.ts +0 -400
  72. package/src/__tests__/memory-chunk-dual-write.test.ts +0 -453
  73. package/src/__tests__/memory-episode-archive.test.ts +0 -370
  74. package/src/__tests__/memory-episode-dual-write.test.ts +0 -626
  75. package/src/__tests__/memory-observation-archive.test.ts +0 -375
  76. package/src/__tests__/memory-observation-dual-write.test.ts +0 -318
  77. package/src/__tests__/memory-reducer-job.test.ts +0 -538
  78. package/src/__tests__/memory-reducer-scheduling.test.ts +0 -473
  79. package/src/__tests__/memory-reducer-store.test.ts +0 -728
  80. package/src/__tests__/memory-reducer-types.test.ts +0 -707
  81. package/src/__tests__/memory-reducer.test.ts +0 -704
  82. package/src/__tests__/memory-simplified-config.test.ts +0 -281
  83. package/src/__tests__/simplified-memory-e2e.test.ts +0 -666
  84. package/src/__tests__/simplified-memory-runtime.test.ts +0 -616
  85. package/src/config/schemas/memory-simplified.ts +0 -101
  86. package/src/memory/archive-recall.ts +0 -516
  87. package/src/memory/archive-store.ts +0 -400
  88. package/src/memory/brief-formatting.ts +0 -33
  89. package/src/memory/brief-open-loops.ts +0 -266
  90. package/src/memory/brief-time.ts +0 -162
  91. package/src/memory/brief.ts +0 -75
  92. package/src/memory/job-handlers/backfill-simplified-memory.ts +0 -462
  93. package/src/memory/job-handlers/reduce-conversation-memory.ts +0 -229
  94. package/src/memory/migrations/185-memory-brief-state.ts +0 -52
  95. package/src/memory/migrations/186-memory-archive.ts +0 -109
  96. package/src/memory/migrations/187-memory-reducer-checkpoints.ts +0 -19
  97. package/src/memory/reducer-scheduler.ts +0 -242
  98. package/src/memory/reducer-store.ts +0 -271
  99. package/src/memory/reducer-types.ts +0 -106
  100. package/src/memory/reducer.ts +0 -467
  101. package/src/memory/schema/memory-archive.ts +0 -121
  102. package/src/memory/schema/memory-brief.ts +0 -55
package/src/config/env.ts CHANGED
@@ -51,9 +51,15 @@ export function getGatewayPort(): number {
51
51
  return int("GATEWAY_PORT", DEFAULT_GATEWAY_PORT);
52
52
  }
53
53
 
54
- /** Resolve the gateway base URL for internal service-to-service calls. */
54
+ /**
55
+ * Resolve the gateway base URL for internal service-to-service calls.
56
+ *
57
+ * In containerized deployments the gateway runs in a separate container,
58
+ * reachable via `GATEWAY_INTERNAL_URL` (e.g. `http://gateway:7822`).
59
+ * Falls back to `http://127.0.0.1:<GATEWAY_PORT>` for local deployments.
60
+ */
55
61
  export function getGatewayInternalBaseUrl(): string {
56
- return `http://127.0.0.1:${getGatewayPort()}`;
62
+ return str("GATEWAY_INTERNAL_URL") ?? `http://127.0.0.1:${getGatewayPort()}`;
57
63
  }
58
64
 
59
65
  // ── Ingress ──────────────────────────────────────────────────────────────────
@@ -25,14 +25,6 @@
25
25
  "description": "Show the Contacts tab in Settings for viewing and managing contacts",
26
26
  "defaultEnabled": true
27
27
  },
28
- {
29
- "id": "custom-inference-provider",
30
- "scope": "macos",
31
- "key": "custom_inference_provider_enabled",
32
- "label": "Custom Inference Provider",
33
- "description": "Allow selecting a specific LLM provider and model for inference in Your Own mode",
34
- "defaultEnabled": false
35
- },
36
28
  {
37
29
  "id": "email-channel",
38
30
  "scope": "assistant",
@@ -106,18 +106,6 @@ export {
106
106
  MemoryDynamicBudgetConfigSchema,
107
107
  MemoryRetrievalConfigSchema,
108
108
  } from "./schemas/memory-retrieval.js";
109
- export type {
110
- MemorySimplifiedArchiveRecallConfig,
111
- MemorySimplifiedBriefConfig,
112
- MemorySimplifiedConfig,
113
- MemorySimplifiedReducerConfig,
114
- } from "./schemas/memory-simplified.js";
115
- export {
116
- MemorySimplifiedArchiveRecallConfigSchema,
117
- MemorySimplifiedBriefConfigSchema,
118
- MemorySimplifiedConfigSchema,
119
- MemorySimplifiedReducerConfigSchema,
120
- } from "./schemas/memory-simplified.js";
121
109
  export type {
122
110
  MemoryEmbeddingsConfig,
123
111
  MemorySegmentationConfig,
@@ -10,7 +10,6 @@ import {
10
10
  MemorySummarizationConfigSchema,
11
11
  } from "./memory-processing.js";
12
12
  import { MemoryRetrievalConfigSchema } from "./memory-retrieval.js";
13
- import { MemorySimplifiedConfigSchema } from "./memory-simplified.js";
14
13
  import {
15
14
  MemoryEmbeddingsConfigSchema,
16
15
  MemorySegmentationConfigSchema,
@@ -46,9 +45,6 @@ export const MemoryConfigSchema = z
46
45
  summarization: MemorySummarizationConfigSchema.default(
47
46
  MemorySummarizationConfigSchema.parse({}),
48
47
  ),
49
- simplified: MemorySimplifiedConfigSchema.default(
50
- MemorySimplifiedConfigSchema.parse({}),
51
- ),
52
48
  })
53
49
  .describe(
54
50
  "Long-term memory system — stores, retrieves, and manages persistent knowledge across conversations",
@@ -43,7 +43,7 @@ export const DaemonConfigSchema = z
43
43
  .number({ error: "daemon.titleGenerationMaxTokens must be a number" })
44
44
  .int("daemon.titleGenerationMaxTokens must be an integer")
45
45
  .positive("daemon.titleGenerationMaxTokens must be a positive integer")
46
- .default(30)
46
+ .default(50)
47
47
  .describe(
48
48
  "Maximum number of tokens for auto-generated conversation titles",
49
49
  ),
@@ -77,6 +77,10 @@ export const PermissionsConfigSchema = z
77
77
  .describe(
78
78
  "Permission mode — 'strict' requires explicit approval for all operations, 'workspace' allows operations within the workspace",
79
79
  ),
80
+ dangerouslySkipPermissions: z
81
+ .boolean({ error: "permissions.dangerouslySkipPermissions must be a boolean" })
82
+ .default(false)
83
+ .describe("Auto-accept all permission prompts without asking"),
80
84
  })
81
85
  .describe("Permission enforcement mode for tool operations");
82
86
 
@@ -538,12 +538,12 @@ export class ContextWindowManager {
538
538
  }
539
539
 
540
540
  const keepTurns = lo;
541
- const keepFromIndex =
541
+ const rawKeepFromIndex =
542
542
  keepTurns === 0
543
543
  ? messages.length
544
544
  : (userTurnStarts[userTurnStarts.length - keepTurns] ??
545
545
  messages.length);
546
-
546
+ const keepFromIndex = adjustForToolPairs(messages, rawKeepFromIndex);
547
547
  return { keepFromIndex, keepTurns };
548
548
  }
549
549
 
@@ -703,6 +703,57 @@ function isToolResultOnly(message: Message): boolean {
703
703
  );
704
704
  }
705
705
 
706
+ /**
707
+ * Walk the keep boundary backward to ensure tool_use/tool_result pairs are
708
+ * never split across the compaction boundary. If the first kept message is
709
+ * a user message containing tool_result blocks whose matching tool_use blocks
710
+ * live in the preceding (compacted-away) assistant message, include that
711
+ * assistant message in the kept set.
712
+ */
713
+ function adjustForToolPairs(
714
+ messages: Message[],
715
+ keepFromIndex: number,
716
+ ): number {
717
+ let idx = keepFromIndex;
718
+ while (idx > 0) {
719
+ const msg = messages[idx];
720
+ if (!msg || msg.role !== "user") break;
721
+
722
+ // Collect tool_use_ids referenced by tool_results in this user message
723
+ const referencedIds = new Set<string>();
724
+ for (const block of msg.content) {
725
+ if ((block.type === "tool_result" || block.type === "web_search_tool_result") && "tool_use_id" in block) {
726
+ referencedIds.add((block as { tool_use_id: string }).tool_use_id);
727
+ }
728
+ }
729
+ if (referencedIds.size === 0) break;
730
+
731
+ // Check if the preceding assistant message contains matching tool_uses
732
+ const prev = messages[idx - 1];
733
+ if (!prev || prev.role !== "assistant") break;
734
+
735
+ const hasOrphanedPair = prev.content.some(
736
+ (block) =>
737
+ (block.type === "tool_use" || block.type === "server_tool_use") &&
738
+ "id" in block &&
739
+ referencedIds.has((block as { id: string }).id),
740
+ );
741
+ if (!hasOrphanedPair) break;
742
+
743
+ // Include the assistant message
744
+ idx--;
745
+
746
+ // The assistant message may itself be preceded by a tool_result user
747
+ // message that pairs with an even earlier assistant — continue the check
748
+ if (idx > 0 && messages[idx - 1]?.role === "user") {
749
+ idx--;
750
+ } else {
751
+ break;
752
+ }
753
+ }
754
+ return idx;
755
+ }
756
+
706
757
  export function getSummaryFromContextMessage(
707
758
  message: Message | undefined,
708
759
  ): string | null {
@@ -12,7 +12,6 @@ import {
12
12
  } from "node:fs";
13
13
  import { join } from "node:path";
14
14
 
15
- import { getIsContainerized } from "../config/env-registry.js";
16
15
  import { getConfig, invalidateConfigCache } from "../config/loader.js";
17
16
  import { clearEmbeddingBackendCache } from "../memory/embedding-backend.js";
18
17
  import { clearCache as clearTrustCache } from "../permissions/trust-store.js";
@@ -210,9 +209,7 @@ export class ConfigWatcher {
210
209
  );
211
210
  }
212
211
 
213
- if (!getIsContainerized()) {
214
- this.startSignalsWatcher();
215
- }
212
+ this.startSignalsWatcher();
216
213
  this.startSkillsWatchers(onConversationEvict);
217
214
  }
218
215
 
@@ -33,7 +33,6 @@ import {
33
33
  } from "../instrument.js";
34
34
  import { commitAppTurnChanges } from "../memory/app-git-service.js";
35
35
  import { getApp, listAppFiles, resolveAppDir } from "../memory/app-store.js";
36
- import { insertCompactionEpisode } from "../memory/archive-store.js";
37
36
  import {
38
37
  addMessage,
39
38
  deleteMessageById,
@@ -514,12 +513,6 @@ export async function runAgentLoopImpl(
514
513
  compacted.summaryText,
515
514
  ctx.contextCompactedMessageCount,
516
515
  );
517
- dualWriteCompactionEpisode(
518
- ctx.conversationId,
519
- ctx.memoryPolicy.scopeId,
520
- compacted.summaryText,
521
- compacted.summaryOutputTokens,
522
- );
523
516
  onEvent({
524
517
  type: "context_compacted",
525
518
  previousEstimatedInputTokens: compacted.previousEstimatedInputTokens,
@@ -787,12 +780,6 @@ export async function runAgentLoopImpl(
787
780
  step.compactionResult.summaryText,
788
781
  ctx.contextCompactedMessageCount,
789
782
  );
790
- dualWriteCompactionEpisode(
791
- ctx.conversationId,
792
- ctx.memoryPolicy.scopeId,
793
- step.compactionResult.summaryText,
794
- step.compactionResult.summaryOutputTokens,
795
- );
796
783
  onEvent({
797
784
  type: "context_compacted",
798
785
  previousEstimatedInputTokens:
@@ -977,12 +964,6 @@ export async function runAgentLoopImpl(
977
964
  midLoopCompact.summaryText,
978
965
  ctx.contextCompactedMessageCount,
979
966
  );
980
- dualWriteCompactionEpisode(
981
- ctx.conversationId,
982
- ctx.memoryPolicy.scopeId,
983
- midLoopCompact.summaryText,
984
- midLoopCompact.summaryOutputTokens,
985
- );
986
967
  onEvent({
987
968
  type: "context_compacted",
988
969
  previousEstimatedInputTokens:
@@ -1179,12 +1160,6 @@ export async function runAgentLoopImpl(
1179
1160
  step.compactionResult.summaryText,
1180
1161
  ctx.contextCompactedMessageCount,
1181
1162
  );
1182
- dualWriteCompactionEpisode(
1183
- ctx.conversationId,
1184
- ctx.memoryPolicy.scopeId,
1185
- step.compactionResult.summaryText,
1186
- step.compactionResult.summaryOutputTokens,
1187
- );
1188
1163
  onEvent({
1189
1164
  type: "context_compacted",
1190
1165
  previousEstimatedInputTokens:
@@ -1292,12 +1267,6 @@ export async function runAgentLoopImpl(
1292
1267
  emergencyCompact.summaryText,
1293
1268
  ctx.contextCompactedMessageCount,
1294
1269
  );
1295
- dualWriteCompactionEpisode(
1296
- ctx.conversationId,
1297
- ctx.memoryPolicy.scopeId,
1298
- emergencyCompact.summaryText,
1299
- emergencyCompact.summaryOutputTokens,
1300
- );
1301
1270
  onEvent({
1302
1271
  type: "context_compacted",
1303
1272
  previousEstimatedInputTokens:
@@ -1402,12 +1371,6 @@ export async function runAgentLoopImpl(
1402
1371
  emergencyCompact.summaryText,
1403
1372
  ctx.contextCompactedMessageCount,
1404
1373
  );
1405
- dualWriteCompactionEpisode(
1406
- ctx.conversationId,
1407
- ctx.memoryPolicy.scopeId,
1408
- emergencyCompact.summaryText,
1409
- emergencyCompact.summaryOutputTokens,
1410
- );
1411
1374
  onEvent({
1412
1375
  type: "context_compacted",
1413
1376
  previousEstimatedInputTokens:
@@ -1873,26 +1836,3 @@ function collapseRawResponses(rawResponses?: unknown[]): unknown | undefined {
1873
1836
  if (!rawResponses || rawResponses.length === 0) return undefined;
1874
1837
  return rawResponses.length === 1 ? rawResponses[0] : rawResponses;
1875
1838
  }
1876
-
1877
- /**
1878
- * Dual-write a compaction summary as an archive episode so it becomes
1879
- * searchable via vector recall. Called after each successful compaction
1880
- * that produces a new summary.
1881
- */
1882
- function dualWriteCompactionEpisode(
1883
- conversationId: string,
1884
- scopeId: string,
1885
- summaryText: string,
1886
- summaryOutputTokens: number,
1887
- ): void {
1888
- const now = Date.now();
1889
- insertCompactionEpisode({
1890
- conversationId,
1891
- scopeId,
1892
- title: truncate(summaryText, 120, ""),
1893
- summary: summaryText,
1894
- tokenEstimate: summaryOutputTokens,
1895
- startAt: now,
1896
- endAt: now,
1897
- });
1898
- }
@@ -1,8 +1,5 @@
1
1
  import { getConfig } from "../config/loader.js";
2
2
  import { estimatePromptTokens } from "../context/token-estimator.js";
3
- import { buildArchiveRecall } from "../memory/archive-recall.js";
4
- import { compileMemoryBrief } from "../memory/brief.js";
5
- import { getDb } from "../memory/db.js";
6
3
  import { buildMemoryQuery } from "../memory/query-builder.js";
7
4
  import { computeRecallBudget } from "../memory/retrieval-budget.js";
8
5
  import {
@@ -12,11 +9,8 @@ import {
12
9
  import type { ScopePolicyOverride } from "../memory/search/types.js";
13
10
  import type { Message } from "../providers/types.js";
14
11
  import type { Provider } from "../providers/types.js";
15
- import { getLogger } from "../util/logger.js";
16
12
  import type { ServerMessage } from "./message-protocol.js";
17
13
 
18
- const log = getLogger("conversation-memory");
19
-
20
14
  export interface MemoryRecallResult {
21
15
  runMessages: Message[];
22
16
  recall: Awaited<ReturnType<typeof buildMemoryRecall>>;
@@ -121,14 +115,6 @@ export async function prepareMemoryContext(
121
115
 
122
116
  const runtimeConfig = getConfig();
123
117
 
124
- // ── Simplified memory path ──────────────────────────────────────────
125
- // When `memory.simplified.enabled` is true, inject the brief and
126
- // optional archive recall instead of the legacy hybrid pipeline.
127
- if (runtimeConfig.memory?.simplified?.enabled) {
128
- return prepareSimplifiedMemoryContext(ctx, content, userMessageId, onEvent);
129
- }
130
-
131
- // ── Legacy memory path (fallback) ──────────────────────────────────
132
118
  // Memory recall via the V2 hybrid pipeline
133
119
  const recallQuery = buildMemoryQuery(content, ctx.messages);
134
120
  const dynamicBudgetConfig = runtimeConfig.memory?.retrieval?.dynamicBudget;
@@ -221,106 +207,3 @@ export async function prepareMemoryContext(
221
207
  recall,
222
208
  };
223
209
  }
224
-
225
- // ── Simplified memory injection ─────────────────────────────────────────
226
-
227
- /**
228
- * Build simplified memory context for a turn: compiles the `<memory_brief>`
229
- * block and conditionally appends `<supporting_recall>` from the archive.
230
- *
231
- * Non-empty blocks are injected as text content blocks prepended to the
232
- * last user message, following the same injection pattern as the legacy
233
- * pipeline. Stripping is handled by `RUNTIME_INJECTION_PREFIXES` which
234
- * already includes `<memory_brief>`.
235
- */
236
- function prepareSimplifiedMemoryContext(
237
- ctx: MemoryPrepareContext,
238
- content: string,
239
- userMessageId: string,
240
- onEvent: (msg: ServerMessage) => void,
241
- ): MemoryRecallResult {
242
- const start = Date.now();
243
-
244
- // Build a no-op recall result matching the legacy shape.
245
- const noopRecall = (): Awaited<ReturnType<typeof buildMemoryRecall>> =>
246
- ({
247
- enabled: true,
248
- degraded: false,
249
- injectedText: "",
250
- semanticHits: 0,
251
- recencyHits: 0,
252
- mergedCount: 0,
253
- selectedCount: 0,
254
- injectedTokens: 0,
255
- latencyMs: 0,
256
- topCandidates: [],
257
- tier1Count: 0,
258
- tier2Count: 0,
259
- }) as Awaited<ReturnType<typeof buildMemoryRecall>>;
260
-
261
- try {
262
- const db = getDb();
263
-
264
- // Step 1: Build the memory brief
265
- const briefResult = compileMemoryBrief(db, ctx.scopeId, userMessageId);
266
-
267
- // Step 2: Conditionally build supporting recall from the archive
268
- const archiveResult = buildArchiveRecall(ctx.scopeId, content);
269
-
270
- // Step 3: Assemble the injection blocks (non-empty only)
271
- const blocks: string[] = [];
272
- if (briefResult.text.length > 0) {
273
- blocks.push(briefResult.text);
274
- }
275
- if (archiveResult.text.length > 0) {
276
- blocks.push(archiveResult.text);
277
- }
278
-
279
- const latencyMs = Date.now() - start;
280
-
281
- // Emit memory status for the simplified path
282
- onEvent({
283
- type: "memory_status",
284
- enabled: true,
285
- degraded: false,
286
- });
287
-
288
- // Inject non-empty blocks into the last user message
289
- let runMessages = ctx.messages;
290
- if (blocks.length > 0) {
291
- const injectedText = blocks.join("\n\n");
292
- const userTail = ctx.messages[ctx.messages.length - 1];
293
- if (userTail && userTail.role === "user") {
294
- runMessages = injectMemoryRecallAsUserBlock(ctx.messages, injectedText);
295
- }
296
-
297
- log.debug(
298
- {
299
- briefLength: briefResult.text.length,
300
- recallTrigger: archiveResult.trigger,
301
- recallBullets: archiveResult.bullets.length,
302
- latencyMs,
303
- },
304
- "Simplified memory injection completed",
305
- );
306
- }
307
-
308
- return {
309
- runMessages,
310
- recall: {
311
- ...noopRecall(),
312
- injectedText: blocks.length > 0 ? blocks.join("\n\n") : "",
313
- latencyMs,
314
- },
315
- };
316
- } catch (err) {
317
- log.warn({ err }, "Simplified memory injection failed, returning no-op");
318
- return {
319
- runMessages: ctx.messages,
320
- recall: {
321
- ...noopRecall(),
322
- latencyMs: Date.now() - start,
323
- },
324
- };
325
- }
326
- }
@@ -961,8 +961,6 @@ const RUNTIME_INJECTION_PREFIXES = [
961
961
  "<inbound_actor_context>",
962
962
  "<interface_turn_context>",
963
963
  "<turn_context>",
964
- "<memory_brief>",
965
- "<supporting_recall>",
966
964
  "<memory_context __injected>",
967
965
  "<memory_context>", // backward-compat: strip legacy blocks from pre-__injected history
968
966
  "<voice_call_control>",
@@ -23,7 +23,6 @@ import {
23
23
  queueGenerateConversationTitle,
24
24
  UNTITLED_FALLBACK,
25
25
  } from "../../memory/conversation-title-service.js";
26
- import { reduceBeforeSwitch } from "../../memory/reducer-scheduler.js";
27
26
  import * as pendingInteractions from "../../runtime/pending-interactions.js";
28
27
  import { getSubagentManager } from "../../subagent/index.js";
29
28
  import { truncate } from "../../util/truncate.js";
@@ -234,12 +233,6 @@ export async function handleConversationCreate(
234
233
  conversationType: normalizeConversationType(conversation.conversationType),
235
234
  });
236
235
 
237
- // Reduce the previous dirty conversation before processing the initial
238
- // message so its memory is fresh for the next read.
239
- if (msg.initialMessage) {
240
- await reduceBeforeSwitch(conversation.id);
241
- }
242
-
243
236
  // Auto-send the initial message if provided, kick-starting the skill.
244
237
  if (msg.initialMessage) {
245
238
  // Queue title generation eagerly — some processMessage paths (guardian
@@ -350,10 +343,6 @@ export async function switchConversation(
350
343
  return null;
351
344
  }
352
345
 
353
- // Reduce the previous dirty conversation before switching so its memory
354
- // is fresh for the next read.
355
- await reduceBeforeSwitch(conversationId);
356
-
357
346
  // If the target conversation is headless-locked (actively executing a task run),
358
347
  // skip rebinding so tool confirmations stay suppressed.
359
348
  const existingConversation = ctx.conversations.get(conversationId);
@@ -30,7 +30,6 @@ import {
30
30
  getConversationType,
31
31
  getMessages,
32
32
  purgePrivateConversations,
33
- sweepStaleReducerJobs,
34
33
  } from "../memory/conversation-crud.js";
35
34
  import { resolveConversationId } from "../memory/conversation-key-store.js";
36
35
  import { initializeDb } from "../memory/db.js";
@@ -57,8 +56,8 @@ import { assistantEventHub } from "../runtime/assistant-event-hub.js";
57
56
  import { DAEMON_INTERNAL_ASSISTANT_ID } from "../runtime/assistant-scope.js";
58
57
  import {
59
58
  initAuthSigningKey,
60
- loadOrCreateSigningKey,
61
59
  mintPairingBearerToken,
60
+ resolveSigningKey,
62
61
  } from "../runtime/auth/token-service.js";
63
62
  import { ensureVellumGuardianBinding } from "../runtime/guardian-vellum-migration.js";
64
63
  import { RuntimeHttpServer } from "../runtime/http-server.js";
@@ -148,7 +147,7 @@ export async function runDaemon(): Promise<void> {
148
147
  // Load (or generate + persist) the auth signing key so tokens survive
149
148
  // daemon restarts. Must happen after ensureDataDir() creates the
150
149
  // protected directory.
151
- const signingKey = loadOrCreateSigningKey();
150
+ const signingKey = await resolveSigningKey();
152
151
  initAuthSigningKey(signingKey);
153
152
 
154
153
  seedInterfaceFiles();
@@ -213,40 +212,16 @@ export async function runDaemon(): Promise<void> {
213
212
  targetId: summaryId,
214
213
  });
215
214
  }
216
- for (const obsId of deletedMemory.deletedObservationIds) {
217
- enqueueMemoryJob("delete_qdrant_vectors", {
218
- targetType: "observation",
219
- targetId: obsId,
220
- });
221
- }
222
- for (const chunkId of deletedMemory.deletedChunkIds) {
223
- enqueueMemoryJob("delete_qdrant_vectors", {
224
- targetType: "chunk",
225
- targetId: chunkId,
226
- });
227
- }
228
- for (const episodeId of deletedMemory.deletedEpisodeIds) {
229
- enqueueMemoryJob("delete_qdrant_vectors", {
230
- targetType: "episode",
231
- targetId: episodeId,
232
- });
233
- }
234
215
  if (
235
216
  deletedMemory.segmentIds.length > 0 ||
236
217
  deletedMemory.orphanedItemIds.length > 0 ||
237
- deletedMemory.deletedSummaryIds.length > 0 ||
238
- deletedMemory.deletedObservationIds.length > 0 ||
239
- deletedMemory.deletedChunkIds.length > 0 ||
240
- deletedMemory.deletedEpisodeIds.length > 0
218
+ deletedMemory.deletedSummaryIds.length > 0
241
219
  ) {
242
220
  log.info(
243
221
  {
244
222
  segments: deletedMemory.segmentIds.length,
245
223
  orphanedItems: deletedMemory.orphanedItemIds.length,
246
224
  deletedSummaries: deletedMemory.deletedSummaryIds.length,
247
- deletedObservations: deletedMemory.deletedObservationIds.length,
248
- deletedChunks: deletedMemory.deletedChunkIds.length,
249
- deletedEpisodes: deletedMemory.deletedEpisodeIds.length,
250
225
  },
251
226
  "Enqueued Qdrant vector cleanup jobs for purged private conversations",
252
227
  );
@@ -271,24 +246,6 @@ export async function runDaemon(): Promise<void> {
271
246
  );
272
247
  }
273
248
 
274
- // Sweep dirty conversations whose tail messages are already past the
275
- // idle delay — they should have been reduced while the daemon was down.
276
- // Enqueue immediate reducer jobs so the memory worker picks them up.
277
- try {
278
- const sweepCount = sweepStaleReducerJobs();
279
- if (sweepCount > 0) {
280
- log.info(
281
- { sweepCount },
282
- `Enqueued reducer jobs for ${sweepCount} stale dirty conversation(s)`,
283
- );
284
- }
285
- } catch (err) {
286
- log.warn(
287
- { err },
288
- "Startup sweep for stale reducer jobs failed — continuing startup",
289
- );
290
- }
291
-
292
249
  // Ensure a vellum guardian binding exists so the identity system works
293
250
  // without requiring a manual bootstrap step.
294
251
  try {
@@ -1,4 +1,4 @@
1
- import { and, desc, eq, lte, or } from "drizzle-orm";
1
+ import { and, asc, eq, lte, or, sql } from "drizzle-orm";
2
2
  import { v4 as uuid } from "uuid";
3
3
 
4
4
  import { getDb } from "../memory/db.js";
@@ -217,7 +217,10 @@ export function getPendingAndOverdueFollowUps(): BriefFollowUp[] {
217
217
  eq(followups.status, "nudged"),
218
218
  ),
219
219
  )
220
- .orderBy(desc(followups.expectedResponseBy))
220
+ .orderBy(
221
+ sql`CASE WHEN ${followups.expectedResponseBy} IS NULL THEN 1 ELSE 0 END`,
222
+ asc(followups.expectedResponseBy),
223
+ )
221
224
  .all();
222
225
 
223
226
  return rows as BriefFollowUp[];