@vellumai/assistant 0.10.3-dev.202606301742.2b63687 → 0.10.3-dev.202606301940.da6000c

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.
package/openapi.yaml CHANGED
@@ -4632,6 +4632,162 @@ paths:
4632
4632
  required: true
4633
4633
  schema:
4634
4634
  type: string
4635
+ requestBody:
4636
+ required: true
4637
+ content:
4638
+ application/json:
4639
+ schema:
4640
+ type: object
4641
+ properties:
4642
+ provider:
4643
+ $ref: "#/components/schemas/LLMProvider"
4644
+ model:
4645
+ type: string
4646
+ minLength: 1
4647
+ maxTokens:
4648
+ type: integer
4649
+ exclusiveMinimum: 0
4650
+ maximum: 9007199254740991
4651
+ effort:
4652
+ type: string
4653
+ enum:
4654
+ - none
4655
+ - low
4656
+ - medium
4657
+ - high
4658
+ - xhigh
4659
+ - max
4660
+ speed:
4661
+ type: string
4662
+ enum:
4663
+ - standard
4664
+ - fast
4665
+ verbosity:
4666
+ type: string
4667
+ enum:
4668
+ - low
4669
+ - medium
4670
+ - high
4671
+ temperature:
4672
+ anyOf:
4673
+ - type: number
4674
+ minimum: 0
4675
+ maximum: 2
4676
+ - type: "null"
4677
+ topP:
4678
+ anyOf:
4679
+ - type: number
4680
+ minimum: 0
4681
+ maximum: 1
4682
+ - type: "null"
4683
+ thinking:
4684
+ type: object
4685
+ properties:
4686
+ enabled:
4687
+ type: boolean
4688
+ streamThinking:
4689
+ type: boolean
4690
+ level:
4691
+ type: string
4692
+ enum:
4693
+ - minimal
4694
+ - low
4695
+ - medium
4696
+ - high
4697
+ contextWindow:
4698
+ type: object
4699
+ properties:
4700
+ enabled:
4701
+ type: boolean
4702
+ maxInputTokens:
4703
+ type: integer
4704
+ exclusiveMinimum: 0
4705
+ maximum: 9007199254740991
4706
+ targetBudgetRatio:
4707
+ type: number
4708
+ exclusiveMinimum: 0
4709
+ maximum: 1
4710
+ compactThreshold:
4711
+ type: number
4712
+ exclusiveMinimum: 0
4713
+ maximum: 1
4714
+ summaryBudgetRatio:
4715
+ type: number
4716
+ exclusiveMinimum: 0
4717
+ maximum: 1
4718
+ overflowRecovery:
4719
+ type: object
4720
+ properties:
4721
+ enabled:
4722
+ type: boolean
4723
+ safetyMarginRatio:
4724
+ type: number
4725
+ exclusiveMinimum: 0
4726
+ exclusiveMaximum: 1
4727
+ maxAttempts:
4728
+ type: integer
4729
+ exclusiveMinimum: 0
4730
+ maximum: 9007199254740991
4731
+ interactiveLatestTurnCompression:
4732
+ type: string
4733
+ enum:
4734
+ - truncate
4735
+ - summarize
4736
+ - drop
4737
+ nonInteractiveLatestTurnCompression:
4738
+ type: string
4739
+ enum:
4740
+ - truncate
4741
+ - summarize
4742
+ - drop
4743
+ openrouter:
4744
+ type: object
4745
+ properties:
4746
+ only:
4747
+ type: array
4748
+ items:
4749
+ type: string
4750
+ minLength: 1
4751
+ logitBias:
4752
+ type: string
4753
+ enum:
4754
+ - suppress-cjk
4755
+ disableCache:
4756
+ type: boolean
4757
+ source:
4758
+ type: string
4759
+ enum:
4760
+ - managed
4761
+ - user
4762
+ label:
4763
+ anyOf:
4764
+ - type: string
4765
+ minLength: 1
4766
+ - type: "null"
4767
+ description:
4768
+ type: string
4769
+ provider_connection:
4770
+ type: string
4771
+ minLength: 1
4772
+ status:
4773
+ anyOf:
4774
+ - $ref: "#/components/schemas/ProfileStatus"
4775
+ - type: "null"
4776
+ mix:
4777
+ minItems: 2
4778
+ type: array
4779
+ items:
4780
+ type: object
4781
+ properties:
4782
+ profile:
4783
+ type: string
4784
+ minLength: 1
4785
+ weight:
4786
+ type: number
4787
+ exclusiveMinimum: 0
4788
+ required:
4789
+ - profile
4790
+ - weight
4635
4791
  responses:
4636
4792
  "200":
4637
4793
  description: Successful response
@@ -17760,6 +17916,44 @@ paths:
17760
17916
  additionalProperties: false
17761
17917
  backgroundToolNotification:
17762
17918
  type: boolean
17919
+ backgroundToolCompletion:
17920
+ type: object
17921
+ properties:
17922
+ id:
17923
+ type: string
17924
+ toolName:
17925
+ type: string
17926
+ conversationId:
17927
+ type: string
17928
+ command:
17929
+ type: string
17930
+ startedAt:
17931
+ type: number
17932
+ status:
17933
+ type: string
17934
+ enum:
17935
+ - completed
17936
+ - failed
17937
+ - cancelled
17938
+ exitCode:
17939
+ anyOf:
17940
+ - type: number
17941
+ - type: "null"
17942
+ output:
17943
+ type: string
17944
+ completedAt:
17945
+ type: number
17946
+ required:
17947
+ - id
17948
+ - toolName
17949
+ - conversationId
17950
+ - command
17951
+ - startedAt
17952
+ - status
17953
+ - exitCode
17954
+ - output
17955
+ - completedAt
17956
+ additionalProperties: false
17763
17957
  slackMessage:
17764
17958
  type: object
17765
17959
  properties:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vellumai/assistant",
3
- "version": "0.10.3-dev.202606301742.2b63687",
3
+ "version": "0.10.3-dev.202606301940.da6000c",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
@@ -189,6 +189,10 @@ describe("bash tool background mode", () => {
189
189
  // Command stdout is fenced as untrusted output, not inlined in the hint.
190
190
  expect(wakeCall.untrustedOutput?.content).toContain("bg_output_12345");
191
191
  expect(wakeCall.untrustedOutput?.source).toBe("tool_result");
192
+ // Durable completion record stamped onto the persisted wake.
193
+ expect(wakeCall.backgroundToolCompletion?.id).toBe("bg-test1234");
194
+ expect(wakeCall.backgroundToolCompletion?.status).toBe("completed");
195
+ expect(wakeCall.backgroundToolCompletion?.exitCode).toBe(0);
192
196
  });
193
197
 
194
198
  test("failing background process delivers an error hint via wake", async () => {
@@ -210,6 +214,9 @@ describe("bash tool background mode", () => {
210
214
  expect(wakeCall.hint).toContain("bg-test1234");
211
215
  // The command fails with exit code 1, so the hint should reflect failure
212
216
  expect(wakeCall.hint).toContain("exit=1");
217
+ expect(wakeCall.backgroundToolCompletion?.id).toBe("bg-test1234");
218
+ expect(wakeCall.backgroundToolCompletion?.status).toBe("failed");
219
+ expect(wakeCall.backgroundToolCompletion?.exitCode).toBe(1);
213
220
  });
214
221
 
215
222
  test("cancelled background process wakes with the cancellation, not a completed result", async () => {
@@ -234,6 +241,8 @@ describe("bash tool background mode", () => {
234
241
  expect(wakeCall.hint).toContain("cancelled");
235
242
  expect(wakeCall.hint).not.toContain("completed");
236
243
  expect(wakeCall.untrustedOutput?.content).toContain("cancelled");
244
+ expect(wakeCall.backgroundToolCompletion?.id).toBe("bg-test1234");
245
+ expect(wakeCall.backgroundToolCompletion?.status).toBe("cancelled");
237
246
  });
238
247
 
239
248
  test("foreground mode still works when background is not set", async () => {
@@ -118,6 +118,7 @@ mock.module("../daemon/host-bash-proxy.js", () => ({
118
118
  // Import under test — MUST come after mock.module calls.
119
119
  // ---------------------------------------------------------------------------
120
120
 
121
+ import type { CompletedBackgroundTool } from "../tools/background-tool-registry.js";
121
122
  import { hostShellTool } from "../tools/host-terminal/host-shell.js";
122
123
  import type { ToolContext, ToolExecutionResult } from "../tools/types.js";
123
124
 
@@ -258,6 +259,11 @@ describe("host_bash background mode — proxy path", () => {
258
259
  maxChars: 40_000,
259
260
  });
260
261
  expect(wakeCall.source).toBe("background-tool");
262
+ const completion =
263
+ wakeCall.backgroundToolCompletion as CompletedBackgroundTool;
264
+ expect(completion.id).toBe("bg-test-0001");
265
+ expect(completion.status).toBe("completed");
266
+ expect(completion.exitCode).toBeNull();
261
267
  });
262
268
 
263
269
  test("calls wakeAgentForOpportunity on proxy error result", async () => {
@@ -398,6 +404,11 @@ describe("host_bash background mode — direct execution path", () => {
398
404
  expect((wakeCall.untrustedOutput as { content: string }).content).toContain(
399
405
  "hello world",
400
406
  );
407
+ const completion =
408
+ wakeCall.backgroundToolCompletion as CompletedBackgroundTool;
409
+ expect(completion.id).toBe("bg-test-0001");
410
+ expect(completion.status).toBe("completed");
411
+ expect(completion.exitCode).toBe(0);
401
412
  });
402
413
 
403
414
  test("calls wakeAgentForOpportunity with error hint on non-zero exit", async () => {
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Tests for handleListMessages background-tool completion projection.
3
+ *
4
+ * A backgrounded bash/host_bash run posts a `<background_event
5
+ * source="background-tool">` wake on completion, stamped with
6
+ * `metadata.backgroundToolCompletion`. The history projection must surface
7
+ * that structured record (and the `backgroundToolNotification` flag) so the
8
+ * web can rebuild a terminal inline card after a daemon restart.
9
+ */
10
+
11
+ import { beforeEach, describe, expect, mock, test } from "bun:test";
12
+
13
+ mock.module("../util/logger.js", () => ({
14
+ getLogger: () =>
15
+ new Proxy({} as Record<string, unknown>, {
16
+ get: () => () => {},
17
+ }),
18
+ }));
19
+
20
+ mock.module("../config/loader.js", () => ({
21
+ getConfig: () => ({
22
+ ui: {},
23
+ model: "test",
24
+ provider: "test",
25
+ memory: { enabled: false },
26
+ rateLimit: { maxRequestsPerMinute: 0 },
27
+ }),
28
+ }));
29
+
30
+ import { ConversationMessageSchema } from "../api/responses/conversation-message.js";
31
+ import {
32
+ addMessage,
33
+ createConversation,
34
+ } from "../persistence/conversation-crud.js";
35
+ import { getDb } from "../persistence/db-connection.js";
36
+ import { initializeDb } from "../persistence/db-init.js";
37
+ import { handleListMessages } from "../runtime/routes/conversation-routes.js";
38
+
39
+ await initializeDb();
40
+
41
+ function resetTables() {
42
+ const db = getDb();
43
+ db.run("DELETE FROM message_attachments");
44
+ db.run("DELETE FROM attachments");
45
+ db.run("DELETE FROM messages");
46
+ db.run("DELETE FROM conversations");
47
+ }
48
+
49
+ interface ProjectedMessage {
50
+ role: string;
51
+ backgroundToolNotification?: boolean;
52
+ backgroundToolCompletion?: Record<string, unknown>;
53
+ }
54
+
55
+ describe("handleListMessages background-tool completion projection", () => {
56
+ beforeEach(resetTables);
57
+
58
+ test("projects backgroundToolCompletion from the wake row metadata", async () => {
59
+ const conv = createConversation();
60
+ await addMessage(
61
+ conv.id,
62
+ "user",
63
+ JSON.stringify([{ type: "text", text: "run a long command" }]),
64
+ );
65
+
66
+ const completion = {
67
+ id: "bg-1",
68
+ toolName: "bash",
69
+ conversationId: conv.id,
70
+ command: "sleep 5 && echo done",
71
+ startedAt: 1000,
72
+ status: "completed" as const,
73
+ exitCode: 0,
74
+ output: "done\n",
75
+ completedAt: 2000,
76
+ };
77
+ // Mirror persistWakeTriggerMessage: a user-role
78
+ // `<background_event source="background-tool">` row carrying the structured
79
+ // completion. Not flagged `hidden`; the client suppresses it from the
80
+ // transcript via `backgroundToolNotification`.
81
+ await addMessage(
82
+ conv.id,
83
+ "user",
84
+ JSON.stringify([
85
+ {
86
+ type: "text",
87
+ text: '<background_event source="background-tool">Background command completed (id=bg-1, exit=0):</background_event>',
88
+ },
89
+ ]),
90
+ {
91
+ metadata: {
92
+ kind: "background-event",
93
+ backgroundEventSource: "background-tool",
94
+ automated: true,
95
+ backgroundToolCompletion: completion,
96
+ },
97
+ },
98
+ );
99
+
100
+ const response = handleListMessages({
101
+ queryParams: { conversationId: conv.id },
102
+ }) as { messages: ProjectedMessage[] };
103
+
104
+ // Every projected message validates against the wire schema.
105
+ for (const message of response.messages) {
106
+ expect(() => ConversationMessageSchema.parse(message)).not.toThrow();
107
+ }
108
+
109
+ const wakeRow = response.messages.find(
110
+ (m) => m.backgroundToolCompletion !== undefined,
111
+ );
112
+ expect(wakeRow).toBeDefined();
113
+ expect(wakeRow?.backgroundToolNotification).toBe(true);
114
+ expect(wakeRow?.backgroundToolCompletion).toEqual(completion);
115
+ });
116
+
117
+ test("omits backgroundToolCompletion when the row carries no completion metadata", async () => {
118
+ const conv = createConversation();
119
+ await addMessage(
120
+ conv.id,
121
+ "user",
122
+ JSON.stringify([{ type: "text", text: "hello" }]),
123
+ );
124
+ await addMessage(
125
+ conv.id,
126
+ "assistant",
127
+ JSON.stringify([{ type: "text", text: "hi there" }]),
128
+ );
129
+
130
+ const response = handleListMessages({
131
+ queryParams: { conversationId: conv.id },
132
+ }) as { messages: ProjectedMessage[] };
133
+
134
+ expect(response.messages).toHaveLength(2);
135
+ for (const message of response.messages) {
136
+ expect(message.backgroundToolCompletion).toBeUndefined();
137
+ expect(message.backgroundToolNotification).toBeUndefined();
138
+ expect(() => ConversationMessageSchema.parse(message)).not.toThrow();
139
+ }
140
+ });
141
+ });
@@ -16,69 +16,53 @@ describe("onboarding template contracts", () => {
16
16
  expect(bootstrap).toMatch(/^_ Lines starting with _/);
17
17
  });
18
18
 
19
- test("contains identity section", () => {
19
+ test("anchors identity in the pre-chat personality and demands staying in character", () => {
20
20
  const lower = bootstrap.toLowerCase();
21
- expect(lower).toContain("identity");
22
- expect(lower).toContain("colleague");
21
+ expect(lower).toContain("pre-chat");
22
+ expect(lower).toContain("in character");
23
23
  });
24
24
 
25
- test("gathers user context", () => {
25
+ test("opens in character with a move drawn from workspace files", () => {
26
26
  const lower = bootstrap.toLowerCase();
27
- expect(lower).toContain("work role");
28
- expect(lower).toContain("goals");
29
- expect(lower).toContain("tools");
27
+ expect(lower).toContain("you start the conversation");
28
+ expect(lower).toContain("workspace files");
30
29
  });
31
30
 
32
- test("contains cleanup instructions with deletion", () => {
31
+ test("is engaging, inquisitive, and brief", () => {
33
32
  const lower = bootstrap.toLowerCase();
34
- expect(lower).toContain("wrap up");
35
- expect(lower).toContain("delete");
36
- expect(lower).toContain("bootstrap.md");
33
+ expect(lower).toContain("follow-up question");
34
+ expect(lower).toContain("short");
37
35
  });
38
36
 
39
- test("handles declined fields", () => {
37
+ test("does the task then keeps learning about the user", () => {
40
38
  const lower = bootstrap.toLowerCase();
41
- expect(lower).toContain("declined");
42
- });
43
-
44
- test("instructs saving to IDENTITY.md, SOUL.md, and user persona file via file_edit", () => {
45
- expect(bootstrap).toContain("IDENTITY.md");
46
- expect(bootstrap).toContain("SOUL.md");
47
- expect(bootstrap).toContain("{{userSlug}}.md");
48
- expect(bootstrap).toContain("file_edit");
49
- });
50
-
51
- test("contains core principle", () => {
52
- expect(bootstrap).toContain("earns its keep");
39
+ expect(lower).toContain("just do it");
40
+ expect(lower).toContain("keep learning about them");
53
41
  });
54
42
 
55
- test("contains opening move with onboarding context", () => {
43
+ test("contains cleanup instructions with deletion", () => {
56
44
  const lower = bootstrap.toLowerCase();
57
- expect(lower).toContain("onboarding");
58
- expect(lower).toContain("json");
59
- expect(lower).toContain("context");
45
+ expect(lower).toContain("delete");
46
+ expect(lower).toContain("bootstrap.md");
60
47
  });
61
48
 
62
- test("contains tone matching guidance", () => {
49
+ test("offers assistant migration from an existing ChatGPT/Claude", () => {
63
50
  const lower = bootstrap.toLowerCase();
64
- expect(lower).toContain("match");
65
- expect(lower).toContain("energy");
51
+ expect(lower).toContain("chatgpt");
52
+ expect(lower).toContain("claude");
53
+ expect(bootstrap).toContain("assistant-migration");
66
54
  });
67
55
 
68
- test("is one-shot", () => {
69
- expect(bootstrap).toContain("One-shot");
56
+ test("does not instruct workspace identity-file writes", () => {
57
+ expect(bootstrap).not.toContain("file_edit");
58
+ expect(bootstrap).not.toContain("file_write");
59
+ expect(bootstrap).not.toMatch(/write .*to SOUL\.md/i);
60
+ expect(bootstrap).not.toMatch(/\{\{userSlug\}\}\.md/);
70
61
  });
71
62
 
72
- test("offers assistant migration during low-signal first openings", () => {
73
- expect(bootstrap).toContain("## Assistant migration");
74
- expect(bootstrap).toContain("onboarding self-introduction");
75
- expect(bootstrap).toContain("treat it as the real first user turn");
76
- expect(bootstrap).toContain(
77
- "If the first real user turn is only a greeting",
78
- );
79
- expect(bootstrap).toContain("that first response is the natural opening");
80
- expect(bootstrap).toContain("assistant-migration");
81
- expect(bootstrap).toContain("do not load or activate");
63
+ test("does not split users into conversation-first and task-first paths", () => {
64
+ expect(bootstrap).not.toMatch(/Path A/);
65
+ expect(bootstrap).not.toMatch(/Path B/);
82
66
  });
83
67
 
84
68
  test("does not contain personality quiz references", () => {
@@ -161,6 +161,7 @@ const BASELINE: Record<string, readonly string[]> = {
161
161
  "../../../config/assistant-feature-flags.js",
162
162
  "../../../config/loader.js",
163
163
  "../../../config/memory-v3-gate.js",
164
+ "../../../config/schema.js",
164
165
  "../../../config/schemas/memory-v2.js",
165
166
  "../../../config/types.js",
166
167
  "../../../contacts/guardian-delivery-reader.js",
@@ -169,10 +170,13 @@ const BASELINE: Record<string, readonly string[]> = {
169
170
  "../../../context/token-estimator.js",
170
171
  "../../../daemon/date-context.js",
171
172
  "../../../daemon/disk-pressure-background-gate.js",
173
+ "../../../daemon/embedding-reconcile.js",
172
174
  "../../../daemon/pkb-context-tracker.js",
173
175
  "../../../daemon/pkb-reminder-builder.js",
176
+ "../../../daemon/skill-memory-refresh.js",
174
177
  "../../../daemon/tool-setup-types.js",
175
178
  "../../../daemon/trust-context.js",
179
+ "../../../jobs/register-job-handlers.js",
176
180
  "../../../permissions/types.js",
177
181
  "../../../persistence/checkpoints.js",
178
182
  "../../../persistence/conversation-crud.js",
@@ -180,7 +184,10 @@ const BASELINE: Record<string, readonly string[]> = {
180
184
  "../../../persistence/embeddings/embedding-backend.js",
181
185
  "../../../persistence/embeddings/embedding-runtime-manager.js",
182
186
  "../../../persistence/embeddings/embedding-types.js",
187
+ "../../../persistence/embeddings/qdrant-client.js",
188
+ "../../../persistence/embeddings/qdrant-manager.js",
183
189
  "../../../persistence/jobs-store.js",
190
+ "../../../persistence/jobs-worker.js",
184
191
  "../../../persistence/memory-lifecycle-hooks.js",
185
192
  "../../../persistence/message-content.js",
186
193
  "../../../persistence/raw-query.js",
@@ -457,9 +457,10 @@ describe("buildSystemPrompt", () => {
457
457
  expect(result).not.toContain("{{userSlug}}");
458
458
  });
459
459
 
460
- test("substitutes the unmodified bundled BOOTSTRAP.md template", () => {
461
- // Copy the real bundled BOOTSTRAP.md into the test workspace so we
462
- // verify substitution against the actual template the daemon ships.
460
+ test("leaves no unresolved placeholders in the bundled BOOTSTRAP.md template", () => {
461
+ // Render the real bundled BOOTSTRAP.md the daemon ships and verify
462
+ // substitution leaves no leftover {{userSlug}} placeholder, whether or
463
+ // not the current template happens to reference it.
463
464
  mockPersona.userSlug = "alice";
464
465
  const bundled = readFileSync(
465
466
  join(import.meta.dirname, "..", "prompts", "templates", "BOOTSTRAP.md"),
@@ -467,7 +468,6 @@ describe("buildSystemPrompt", () => {
467
468
  );
468
469
  writeFileSync(join(TEST_DIR, "BOOTSTRAP.md"), bundled);
469
470
  const result = buildSystemPrompt();
470
- expect(result).toContain("users/alice.md");
471
471
  expect(result).not.toContain("{{userSlug}}");
472
472
  });
473
473
  });
package/src/api/index.ts CHANGED
@@ -404,6 +404,7 @@ export {
404
404
  DictationRequestSchema,
405
405
  } from "./requests/dictation.js";
406
406
  export {
407
+ type BackgroundToolCompletion,
407
408
  type ConversationAttachmentBlock,
408
409
  ConversationAttachmentBlockSchema,
409
410
  type ConversationContentBlock,
@@ -296,6 +296,29 @@ export type ConversationAcpNotification = z.infer<
296
296
  typeof ConversationAcpNotificationSchema
297
297
  >;
298
298
 
299
+ // ---------------------------------------------------------------------------
300
+ // Background-tool completion
301
+ // ---------------------------------------------------------------------------
302
+
303
+ /** Structured terminal record of a backgrounded bash/host_bash run, carrying
304
+ * everything a web `BackgroundTaskEntry` needs to rebuild a completed inline
305
+ * card from history. Mirrors the `background_tool_completed` SSE event plus
306
+ * the registry fields (`toolName`, `command`, `startedAt`). */
307
+ export const BackgroundToolCompletionSchema = z.object({
308
+ id: z.string(),
309
+ toolName: z.string(),
310
+ conversationId: z.string(),
311
+ command: z.string(),
312
+ startedAt: z.number(),
313
+ status: z.enum(["completed", "failed", "cancelled"]),
314
+ exitCode: z.number().nullable(),
315
+ output: z.string(),
316
+ completedAt: z.number(),
317
+ });
318
+ export type BackgroundToolCompletion = z.infer<
319
+ typeof BackgroundToolCompletionSchema
320
+ >;
321
+
299
322
  // ---------------------------------------------------------------------------
300
323
  // Slack message envelope
301
324
  // ---------------------------------------------------------------------------
@@ -549,6 +572,12 @@ export const ConversationMessageSchema = z.object({
549
572
  * notifications, the row stays in state (the LLM reads it) but is filtered
550
573
  * from the rendered transcript — the inline card carries the status. */
551
574
  backgroundToolNotification: z.boolean().optional(),
575
+ /** Structured completion of a backgrounded bash/host_bash run, stamped on the
576
+ * same persisted background-event wake row as `backgroundToolNotification`.
577
+ * Lets the web reconstruct a terminal inline card after a daemon restart
578
+ * (the in-memory completed ring does not survive restarts). `id` equals the
579
+ * spawning tool call's `{backgrounded,id}` id. */
580
+ backgroundToolCompletion: BackgroundToolCompletionSchema.optional(),
552
581
  slackMessage: ConversationSlackMessageSchema.optional(),
553
582
  /**
554
583
  * Queue state for a user message that is still waiting in the daemon's
@@ -22,7 +22,7 @@ describe("MemoryV2ConfigSchema", () => {
22
22
  sparse_weight: 0.15,
23
23
  bm25_k1: 1.2,
24
24
  bm25_b: 0.4,
25
- consolidation_interval_hours: 4,
25
+ consolidation_interval_hours: 8,
26
26
  consolidation_max_buffer_lines: 100,
27
27
  consolidation_max_entries_per_run: 150,
28
28
  max_page_chars: 5000,
@@ -222,7 +222,7 @@ describe("MemoryConfigSchema integration with v2 block", () => {
222
222
  expect(parsed.v2.d).toBe(0.3);
223
223
  expect(parsed.v2.dense_weight).toBe(0.85);
224
224
  expect(parsed.v2.sparse_weight).toBe(0.15);
225
- expect(parsed.v2.consolidation_interval_hours).toBe(4);
225
+ expect(parsed.v2.consolidation_interval_hours).toBe(8);
226
226
  expect(parsed.v2.max_page_chars).toBe(5000);
227
227
  });
228
228
 
@@ -190,7 +190,7 @@ export const MemoryV2ConfigSchema = z
190
190
  .positive(
191
191
  "memory.v2.consolidation_interval_hours must be a positive integer",
192
192
  )
193
- .default(4)
193
+ .default(8)
194
194
  .describe(
195
195
  "Hours between scheduled consolidation runs that synthesize buffered memories into concept pages",
196
196
  ),