@gmickel/gno 1.18.0 → 1.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (153) hide show
  1. package/README.md +14 -7
  2. package/assets/skill/SKILL.md +54 -12
  3. package/assets/skill/mcp-reference.md +7 -2
  4. package/assets/skill/recipes/citation-and-provenance.md +32 -9
  5. package/package.json +2 -1
  6. package/spec/AGENTS.md +83 -0
  7. package/spec/CLAUDE.md +83 -0
  8. package/spec/bench-fixture.schema.json +137 -0
  9. package/spec/cli.md +2919 -0
  10. package/spec/db/schema.sql +442 -0
  11. package/spec/evals-agentic.md +592 -0
  12. package/spec/evals.md +1106 -0
  13. package/spec/mcp.md +2279 -0
  14. package/spec/output-schemas/activation-verification.schema.json +515 -0
  15. package/spec/output-schemas/ask.schema.json +564 -0
  16. package/spec/output-schemas/backlinks.schema.json +131 -0
  17. package/spec/output-schemas/bench-result.schema.json +120 -0
  18. package/spec/output-schemas/capture-receipt.schema.json +143 -0
  19. package/spec/output-schemas/claim-verification.schema.json +291 -0
  20. package/spec/output-schemas/collection-list.schema.json +45 -0
  21. package/spec/output-schemas/context-capsule-v1.schema.json +726 -0
  22. package/spec/output-schemas/context-capsule-verification.schema.json +1338 -0
  23. package/spec/output-schemas/context-list.schema.json +21 -0
  24. package/spec/output-schemas/doctor.schema.json +313 -0
  25. package/spec/output-schemas/error.schema.json +30 -0
  26. package/spec/output-schemas/expansion.schema.json +37 -0
  27. package/spec/output-schemas/get.schema.json +140 -0
  28. package/spec/output-schemas/graph-query.schema.json +99 -0
  29. package/spec/output-schemas/graph.schema.json +371 -0
  30. package/spec/output-schemas/links-list.schema.json +186 -0
  31. package/spec/output-schemas/mcp-add-collection-result.schema.json +23 -0
  32. package/spec/output-schemas/mcp-capture-result.schema.json +152 -0
  33. package/spec/output-schemas/mcp-http-error.schema.json +30 -0
  34. package/spec/output-schemas/mcp-job-list.schema.json +58 -0
  35. package/spec/output-schemas/mcp-job-status.schema.json +224 -0
  36. package/spec/output-schemas/mcp-remove-result.schema.json +39 -0
  37. package/spec/output-schemas/mcp-sync-result.schema.json +41 -0
  38. package/spec/output-schemas/mcp-tag-result.schema.json +33 -0
  39. package/spec/output-schemas/models-list.schema.json +93 -0
  40. package/spec/output-schemas/multi-get.schema.json +103 -0
  41. package/spec/output-schemas/process-status.schema.json +119 -0
  42. package/spec/output-schemas/query-diagnose.schema.json +123 -0
  43. package/spec/output-schemas/resident-status.schema.json +154 -0
  44. package/spec/output-schemas/retrieval-trace-common.schema.json +492 -0
  45. package/spec/output-schemas/retrieval-trace-delete.schema.json +16 -0
  46. package/spec/output-schemas/retrieval-trace-export.schema.json +61 -0
  47. package/spec/output-schemas/retrieval-trace-filters.schema.json +139 -0
  48. package/spec/output-schemas/retrieval-trace-judgment.schema.json +15 -0
  49. package/spec/output-schemas/retrieval-trace-list.schema.json +18 -0
  50. package/spec/output-schemas/retrieval-trace-payloads.schema.json +178 -0
  51. package/spec/output-schemas/retrieval-trace-purge.schema.json +31 -0
  52. package/spec/output-schemas/retrieval-trace-qrels.schema.json +303 -0
  53. package/spec/output-schemas/retrieval-trace-replay.schema.json +286 -0
  54. package/spec/output-schemas/retrieval-trace-show.schema.json +69 -0
  55. package/spec/output-schemas/retrieval-trace-summary.schema.json +65 -0
  56. package/spec/output-schemas/search-result.schema.json +154 -0
  57. package/spec/output-schemas/search-results.schema.json +338 -0
  58. package/spec/output-schemas/similar.schema.json +84 -0
  59. package/spec/output-schemas/status.schema.json +676 -0
  60. package/spec/output-schemas/tags-list.schema.json +48 -0
  61. package/src/app/context-runtime-contract.ts +10 -5
  62. package/src/app/context-runtime-input.ts +29 -1
  63. package/src/app/context-runtime-types.ts +7 -0
  64. package/src/app/context-runtime.ts +20 -2
  65. package/src/app/context-surface.ts +4 -0
  66. package/src/app/verified-ask.ts +291 -0
  67. package/src/cli/commands/ask-format.ts +255 -0
  68. package/src/cli/commands/ask.ts +144 -183
  69. package/src/cli/commands/context-build.ts +56 -9
  70. package/src/cli/commands/get.ts +64 -3
  71. package/src/cli/commands/query.ts +62 -23
  72. package/src/cli/commands/replay.ts +140 -0
  73. package/src/cli/commands/search.ts +48 -3
  74. package/src/cli/commands/shared.ts +3 -1
  75. package/src/cli/commands/trace.ts +200 -0
  76. package/src/cli/commands/vsearch.ts +75 -53
  77. package/src/cli/program.ts +287 -1
  78. package/src/config/index.ts +9 -0
  79. package/src/config/retrieval-traces.ts +56 -0
  80. package/src/config/types.ts +4 -0
  81. package/src/core/context-budget.ts +6 -0
  82. package/src/core/context-capsule-retrieval-schema.ts +4 -0
  83. package/src/core/context-capsule-schema.ts +17 -0
  84. package/src/core/context-capsule-validation.ts +3 -2
  85. package/src/core/context-capsule.ts +18 -0
  86. package/src/core/context-compiler.ts +44 -25
  87. package/src/core/context-evidence.ts +6 -0
  88. package/src/core/retrieval-qrels.ts +405 -0
  89. package/src/core/retrieval-replay-candidate.ts +368 -0
  90. package/src/core/retrieval-replay-types.ts +109 -0
  91. package/src/core/retrieval-replay-validation.ts +89 -0
  92. package/src/core/retrieval-replay.ts +441 -0
  93. package/src/core/retrieval-trace-evidence-origin.ts +178 -0
  94. package/src/core/retrieval-trace-export.ts +113 -0
  95. package/src/core/retrieval-trace-filter-normalization.ts +27 -0
  96. package/src/core/retrieval-trace-filters.ts +19 -0
  97. package/src/core/retrieval-trace-management-helpers.ts +247 -0
  98. package/src/core/retrieval-trace-management-types.ts +132 -0
  99. package/src/core/retrieval-trace-management.ts +422 -0
  100. package/src/core/retrieval-trace-request.ts +141 -0
  101. package/src/core/retrieval-trace-session.ts +507 -0
  102. package/src/core/retrieval-trace.ts +472 -0
  103. package/src/llm/errors.ts +10 -1
  104. package/src/llm/httpGeneration.ts +11 -1
  105. package/src/llm/nodeLlamaCpp/generation.ts +54 -10
  106. package/src/llm/types.ts +6 -0
  107. package/src/mcp/tools/ask.ts +228 -0
  108. package/src/mcp/tools/context.ts +87 -15
  109. package/src/mcp/tools/get.ts +35 -1
  110. package/src/mcp/tools/index.ts +83 -0
  111. package/src/mcp/tools/query.ts +95 -64
  112. package/src/mcp/tools/search.ts +36 -13
  113. package/src/mcp/tools/trace.ts +143 -0
  114. package/src/mcp/tools/vsearch.ts +71 -38
  115. package/src/pipeline/answer.ts +167 -26
  116. package/src/pipeline/claim-verification-schema.ts +235 -0
  117. package/src/pipeline/claim-verification.ts +487 -0
  118. package/src/pipeline/claim-verifier.ts +474 -0
  119. package/src/pipeline/graph-retrieval.ts +15 -1
  120. package/src/pipeline/hybrid.ts +151 -43
  121. package/src/pipeline/search.ts +36 -3
  122. package/src/pipeline/trace-metadata.ts +47 -0
  123. package/src/pipeline/types.ts +68 -0
  124. package/src/pipeline/vsearch.ts +101 -38
  125. package/src/sdk/client.ts +415 -73
  126. package/src/sdk/documents.ts +48 -1
  127. package/src/sdk/index.ts +17 -0
  128. package/src/sdk/types.ts +28 -0
  129. package/src/serve/context-capsule.ts +67 -8
  130. package/src/serve/public/app.tsx +12 -1
  131. package/src/serve/public/components/AskVerificationPanel.tsx +189 -0
  132. package/src/serve/public/globals.built.css +1 -1
  133. package/src/serve/public/lib/workspace-tabs.ts +2 -0
  134. package/src/serve/public/pages/Ask.tsx +42 -4
  135. package/src/serve/public/pages/Dashboard.tsx +10 -0
  136. package/src/serve/public/pages/TraceHistory.tsx +478 -0
  137. package/src/serve/public/pages/trace-history-detail.tsx +224 -0
  138. package/src/serve/retrieval-trace.ts +28 -0
  139. package/src/serve/routes/api.ts +508 -68
  140. package/src/serve/routes/traces.ts +156 -0
  141. package/src/serve/server.ts +87 -2
  142. package/src/store/index.ts +31 -0
  143. package/src/store/migrations/014-retrieval-traces.ts +303 -0
  144. package/src/store/migrations/index.ts +2 -0
  145. package/src/store/retrieval-trace-codec.ts +384 -0
  146. package/src/store/sqlite/adapter.ts +153 -1
  147. package/src/store/sqlite/retrieval-trace-management-store.ts +341 -0
  148. package/src/store/sqlite/retrieval-trace-retention.ts +349 -0
  149. package/src/store/sqlite/retrieval-trace-rows.ts +267 -0
  150. package/src/store/sqlite/retrieval-trace-store.ts +515 -0
  151. package/src/store/types.ts +297 -0
  152. package/src/store/vector/sqlite-vec.ts +76 -1
  153. package/src/store/vector/types.ts +1 -1
@@ -5,8 +5,18 @@
5
5
  * @module src/cli/commands/vsearch
6
6
  */
7
7
 
8
+ import type {
9
+ RetrievalTraceSession,
10
+ RetrievalTraceSurfaceMetadata,
11
+ } from "../../core/retrieval-trace-session";
12
+ import type { EmbeddingPort } from "../../llm/types";
8
13
  import type { SearchOptions, SearchResults } from "../../pipeline/types";
9
14
 
15
+ import {
16
+ finishRetrievalTraceAfterError,
17
+ retrievalTraceFilters,
18
+ startRetrievalTraceRequest,
19
+ } from "../../core/retrieval-trace-request";
10
20
  import { LlmAdapter } from "../../llm/nodeLlamaCpp/adapter";
11
21
  import { resolveModelUri } from "../../llm/registry";
12
22
  import { formatQueryForEmbedding } from "../../pipeline/contextual";
@@ -47,7 +57,11 @@ export type VsearchCommandOptions = SearchOptions & {
47
57
  };
48
58
 
49
59
  export type VsearchResult =
50
- | { success: true; data: SearchResults }
60
+ | {
61
+ success: true;
62
+ data: SearchResults;
63
+ metadata?: RetrievalTraceSurfaceMetadata;
64
+ }
51
65
  | { success: false; error: string };
52
66
 
53
67
  // ─────────────────────────────────────────────────────────────────────────────
@@ -78,6 +92,8 @@ export async function vsearch(
78
92
  }
79
93
 
80
94
  const { store, config } = initResult;
95
+ let embedPort: EmbeddingPort | null = null;
96
+ let traceSession: RetrievalTraceSession | undefined;
81
97
 
82
98
  try {
83
99
  // Get model URI from preset
@@ -87,67 +103,74 @@ export async function vsearch(
87
103
  options.model,
88
104
  options.collection
89
105
  );
106
+ const traceStart = await startRetrievalTraceRequest({
107
+ store,
108
+ config,
109
+ query,
110
+ filters: retrievalTraceFilters({ ...options, limit }),
111
+ pipeline: "vector",
112
+ indexName: options.indexName,
113
+ modelUris: [modelUri],
114
+ });
115
+ if (!traceStart.ok) {
116
+ return { success: false, error: traceStart.error.message };
117
+ }
118
+ traceSession = traceStart.value ?? undefined;
90
119
 
91
120
  // Create LLM adapter for embeddings
92
121
  const llm = new LlmAdapter(config);
93
122
  const embedResult = await llm.createEmbeddingPort(modelUri);
94
123
  if (!embedResult.ok) {
124
+ await traceSession?.finish("failed");
95
125
  return { success: false, error: embedResult.error.message };
96
126
  }
97
127
 
98
- const embedPort = embedResult.value;
99
-
100
- try {
101
- // Embed query with contextual formatting (also determines dimensions)
102
- const queryEmbedResult = await embedPort.embed(
103
- formatQueryForEmbedding(query, embedPort.modelUri)
104
- );
105
- if (!queryEmbedResult.ok) {
106
- return { success: false, error: queryEmbedResult.error.message };
107
- }
108
- const queryEmbedding = new Float32Array(queryEmbedResult.value);
109
- const dimensions = queryEmbedding.length;
110
-
111
- // Create vector index port
112
- const db = store.getRawDb();
113
- const vectorResult = await createVectorIndexPort(db, {
114
- model: modelUri,
115
- dimensions,
116
- });
117
-
118
- if (!vectorResult.ok) {
119
- return { success: false, error: vectorResult.error.message };
120
- }
121
-
122
- const vectorIndex = vectorResult.value;
123
-
124
- const deps: VectorSearchDeps = {
125
- store,
126
- vectorIndex,
127
- embedPort,
128
- config,
129
- };
130
-
131
- // Pass pre-computed embedding to avoid double-embed
132
- const result = await searchVectorWithEmbedding(
133
- deps,
134
- query,
135
- queryEmbedding,
136
- { ...options, limit }
137
- );
138
-
139
- if (!result.ok) {
140
- return { success: false, error: result.error.message };
141
- }
142
-
143
- return {
144
- success: true,
145
- data: decorateSearchResultsForIndex(result.value, options.indexName),
146
- };
147
- } finally {
148
- await embedPort.dispose();
128
+ embedPort = embedResult.value;
129
+ const queryEmbedResult = await embedPort.embed(
130
+ formatQueryForEmbedding(query, embedPort.modelUri)
131
+ );
132
+ if (!queryEmbedResult.ok) {
133
+ await traceSession?.finish("failed");
134
+ return { success: false, error: queryEmbedResult.error.message };
149
135
  }
136
+ const queryEmbedding = new Float32Array(queryEmbedResult.value);
137
+ const vectorResult = await createVectorIndexPort(store.getRawDb(), {
138
+ model: modelUri,
139
+ dimensions: queryEmbedding.length,
140
+ });
141
+ if (!vectorResult.ok) {
142
+ await traceSession?.finish("failed");
143
+ return { success: false, error: vectorResult.error.message };
144
+ }
145
+ const deps: VectorSearchDeps = {
146
+ store,
147
+ vectorIndex: vectorResult.value,
148
+ embedPort,
149
+ config,
150
+ };
151
+ const result = await searchVectorWithEmbedding(
152
+ deps,
153
+ query,
154
+ queryEmbedding,
155
+ { ...options, limit, traceSession }
156
+ );
157
+ if (!result.ok) {
158
+ await traceSession?.finish("failed");
159
+ return { success: false, error: result.error.message };
160
+ }
161
+ return {
162
+ success: true,
163
+ data: decorateSearchResultsForIndex(result.value, options.indexName),
164
+ metadata: traceSession?.metadata(),
165
+ };
166
+ } catch (cause) {
167
+ await finishRetrievalTraceAfterError(traceSession, cause);
168
+ return {
169
+ success: false,
170
+ error: cause instanceof Error ? cause.message : "Vector search failed",
171
+ };
150
172
  } finally {
173
+ await embedPort?.dispose();
151
174
  await store.close();
152
175
  }
153
176
  }
@@ -194,7 +217,6 @@ export function formatVsearch(
194
217
  })
195
218
  : `Error: ${result.error}`;
196
219
  }
197
-
198
220
  const formatOpts: FormatOptions = {
199
221
  format: getFormatType(options),
200
222
  full: options.full,
@@ -164,6 +164,15 @@ async function writeOutput(
164
164
  }
165
165
  }
166
166
 
167
+ /** Emit opt-in trace identity without changing command stdout payloads. */
168
+ export function writeRetrievalTraceReceipt(
169
+ metadata: { traceId: string } | undefined
170
+ ): void {
171
+ if (metadata) {
172
+ process.stderr.write(`Trace: ${metadata.traceId}\n`);
173
+ }
174
+ }
175
+
167
176
  async function resolveTerminalLinkPolicy(
168
177
  format: "terminal" | "json" | "files" | "csv" | "md" | "xml"
169
178
  ): Promise<
@@ -292,6 +301,7 @@ export function createProgram(): Command {
292
301
  wireOnboardingCommands(program);
293
302
  wireCaptureCommand(program);
294
303
  wireManagementCommands(program);
304
+ wireTraceCommands(program);
295
305
  wirePublishCommand(program);
296
306
  wireVecCommands(program);
297
307
  wireRetrievalCommands(program);
@@ -315,6 +325,244 @@ Report issues: ${ISSUES_URL}`
315
325
  return program;
316
326
  }
317
327
 
328
+ function wireTraceCommands(program: Command): void {
329
+ const traceCmd = program
330
+ .command("trace")
331
+ .description("Inspect and manage private local retrieval traces");
332
+
333
+ const outputFormat = (options: Record<string, unknown>): "json" | "md" =>
334
+ getFormat(options) === "json" ? "json" : "md";
335
+
336
+ traceCmd
337
+ .command("list")
338
+ .description("List bounded, redacted retrieval trace summaries")
339
+ .option("-n, --limit <num>", "maximum traces", "50")
340
+ .option("--cursor <cursor>", "continue from a previous list receipt")
341
+ .option("--json", "JSON output")
342
+ .option("--md", "Markdown output")
343
+ .action(async (cmdOpts: Record<string, unknown>) => {
344
+ const globals = getGlobals();
345
+ const { traceList } = await import("./commands/trace");
346
+ const output = await traceList(
347
+ {
348
+ limit: parsePositiveInt("limit", cmdOpts.limit),
349
+ cursor: cmdOpts.cursor as string | undefined,
350
+ },
351
+ {
352
+ configPath: globals.config,
353
+ indexName: globals.index,
354
+ format: outputFormat(cmdOpts),
355
+ }
356
+ );
357
+ process.stdout.write(output);
358
+ });
359
+
360
+ traceCmd
361
+ .command("show <trace-id>")
362
+ .description("Inspect one bounded retrieval trace receipt")
363
+ .option("--detail-limit <num>", "maximum records per detail section", "500")
364
+ .option("--json", "JSON output")
365
+ .option("--md", "Markdown output")
366
+ .action(async (traceId: string, cmdOpts: Record<string, unknown>) => {
367
+ const globals = getGlobals();
368
+ const { traceShow } = await import("./commands/trace");
369
+ const output = await traceShow(
370
+ traceId,
371
+ {
372
+ detailLimit: parsePositiveInt("detail-limit", cmdOpts.detailLimit),
373
+ },
374
+ {
375
+ configPath: globals.config,
376
+ indexName: globals.index,
377
+ format: outputFormat(cmdOpts),
378
+ }
379
+ );
380
+ process.stdout.write(output);
381
+ });
382
+
383
+ traceCmd
384
+ .command("label <trace-id>")
385
+ .description("Append an explicit retrieval relevance judgment")
386
+ .requiredOption(
387
+ "--label <label>",
388
+ "relevant, irrelevant, or missing-expected"
389
+ )
390
+ .requiredOption("--target <ref>", "evidence or expected-document reference")
391
+ .option("--target-kind <kind>", "document, chunk, or span")
392
+ .option("--from-line <num>", "exact evidence start line")
393
+ .option("--to-line <num>", "exact evidence end line")
394
+ .option("--source-hash <sha256>", "expected immutable source hash")
395
+ .option("--docid <docid>", "expected document ID")
396
+ .option("--idempotency-key <key>", "caller retry key")
397
+ .option("--json", "JSON output")
398
+ .option("--md", "Markdown output")
399
+ .action(async (traceId: string, cmdOpts: Record<string, unknown>) => {
400
+ const globals = getGlobals();
401
+ const rawLabel = String(cmdOpts.label).replace("-", "_");
402
+ if (!["relevant", "irrelevant", "missing_expected"].includes(rawLabel)) {
403
+ throw new CliError(
404
+ "VALIDATION",
405
+ "--label must be relevant, irrelevant, or missing-expected"
406
+ );
407
+ }
408
+ const rawTargetKind = cmdOpts.targetKind;
409
+ if (
410
+ rawTargetKind !== undefined &&
411
+ (typeof rawTargetKind !== "string" ||
412
+ !["document", "chunk", "span"].includes(rawTargetKind))
413
+ ) {
414
+ throw new CliError(
415
+ "VALIDATION",
416
+ "--target-kind must be document, chunk, or span"
417
+ );
418
+ }
419
+ const { traceLabel } = await import("./commands/trace");
420
+ const output = await traceLabel(
421
+ {
422
+ traceId,
423
+ label: rawLabel as "relevant" | "irrelevant" | "missing_expected",
424
+ targetRef: String(cmdOpts.target),
425
+ targetKind: rawTargetKind as
426
+ | "document"
427
+ | "chunk"
428
+ | "span"
429
+ | undefined,
430
+ startLine:
431
+ cmdOpts.fromLine === undefined
432
+ ? undefined
433
+ : parsePositiveInt("from-line", cmdOpts.fromLine),
434
+ endLine:
435
+ cmdOpts.toLine === undefined
436
+ ? undefined
437
+ : parsePositiveInt("to-line", cmdOpts.toLine),
438
+ sourceHash: cmdOpts.sourceHash as string | undefined,
439
+ docid: cmdOpts.docid as string | undefined,
440
+ idempotencyKey: cmdOpts.idempotencyKey as string | undefined,
441
+ },
442
+ {
443
+ configPath: globals.config,
444
+ indexName: globals.index,
445
+ format: outputFormat(cmdOpts),
446
+ }
447
+ );
448
+ process.stdout.write(output);
449
+ });
450
+
451
+ traceCmd
452
+ .command("export <trace-ids...>")
453
+ .description("Export immutable terminal traces as one local receipt")
454
+ .option("--format <format>", "agentic-receipt or qrels", "agentic-receipt")
455
+ .option("--output <path>", "write canonical artifact atomically")
456
+ .option("--json", "JSON output")
457
+ .action(async (traceIds: string[], cmdOpts: Record<string, unknown>) => {
458
+ const globals = getGlobals();
459
+ const exportFormat = String(cmdOpts.format);
460
+ if (!["agentic-receipt", "qrels"].includes(exportFormat)) {
461
+ throw new CliError(
462
+ "VALIDATION",
463
+ "--format must be agentic-receipt or qrels"
464
+ );
465
+ }
466
+ const { traceExport } = await import("./commands/trace");
467
+ process.stdout.write(
468
+ await traceExport(traceIds, {
469
+ configPath: globals.config,
470
+ indexName: globals.index,
471
+ format: "json",
472
+ output: cmdOpts.output as string | undefined,
473
+ exportFormat: exportFormat as "agentic-receipt" | "qrels",
474
+ })
475
+ );
476
+ });
477
+
478
+ traceCmd
479
+ .command("replay <export-id>")
480
+ .description("Compare one immutable qrels baseline with a candidate")
481
+ .requiredOption("--candidate <type>", "bm25, vector, or hybrid")
482
+ .option("-n, --limit <num>", "result cutoff")
483
+ .option("--candidate-limit <num>", "candidate pool size")
484
+ .option("--no-expand", "disable query expansion")
485
+ .option("--no-rerank", "disable reranking")
486
+ .option("--json", "JSON output")
487
+ .option("--md", "Markdown output")
488
+ .action(async (exportId: string, cmdOpts: Record<string, unknown>) => {
489
+ const globals = getGlobals();
490
+ const candidateType = String(cmdOpts.candidate);
491
+ if (!["bm25", "vector", "hybrid"].includes(candidateType)) {
492
+ throw new CliError(
493
+ "VALIDATION",
494
+ "--candidate must be bm25, vector, or hybrid"
495
+ );
496
+ }
497
+ const { traceReplay } = await import("./commands/replay");
498
+ process.stdout.write(
499
+ await traceReplay(
500
+ exportId,
501
+ {
502
+ id: `cli-${candidateType}`,
503
+ type: candidateType as "bm25" | "vector" | "hybrid",
504
+ limit:
505
+ cmdOpts.limit === undefined
506
+ ? undefined
507
+ : parsePositiveInt("limit", cmdOpts.limit),
508
+ candidateLimit:
509
+ cmdOpts.candidateLimit === undefined
510
+ ? undefined
511
+ : parsePositiveInt("candidate-limit", cmdOpts.candidateLimit),
512
+ noExpand: cmdOpts.expand === false,
513
+ noRerank: cmdOpts.rerank === false,
514
+ },
515
+ {
516
+ configPath: globals.config,
517
+ indexName: globals.index,
518
+ format: outputFormat(cmdOpts),
519
+ offline: globals.offline,
520
+ }
521
+ )
522
+ );
523
+ });
524
+
525
+ traceCmd
526
+ .command("delete <trace-id>")
527
+ .description("Delete one trace and every owned local record")
528
+ .option("--json", "JSON output")
529
+ .option("--md", "Markdown output")
530
+ .action(async (traceId: string, cmdOpts: Record<string, unknown>) => {
531
+ const globals = getGlobals();
532
+ const { traceDelete } = await import("./commands/trace");
533
+ process.stdout.write(
534
+ await traceDelete(traceId, {
535
+ configPath: globals.config,
536
+ indexName: globals.index,
537
+ format: outputFormat(cmdOpts),
538
+ })
539
+ );
540
+ });
541
+
542
+ traceCmd
543
+ .command("purge")
544
+ .description("Delete every local retrieval trace receipt")
545
+ .option("--json", "JSON output")
546
+ .option("--md", "Markdown output")
547
+ .action(async (cmdOpts: Record<string, unknown>) => {
548
+ const globals = getGlobals();
549
+ if (!globals.yes) {
550
+ throw new CliError(
551
+ "VALIDATION",
552
+ "Trace purge requires the global --yes confirmation"
553
+ );
554
+ }
555
+ const { tracePurge } = await import("./commands/trace");
556
+ process.stdout.write(
557
+ await tracePurge({
558
+ configPath: globals.config,
559
+ indexName: globals.index,
560
+ format: outputFormat(cmdOpts),
561
+ })
562
+ );
563
+ });
564
+ }
565
+
318
566
  // ─────────────────────────────────────────────────────────────────────────────
319
567
  // Search Commands (search, vsearch, query, ask)
320
568
  // ─────────────────────────────────────────────────────────────────────────────
@@ -387,7 +635,9 @@ function wireSearchCommands(program: Command): void {
387
635
 
388
636
  const limit = cmdOpts.limit
389
637
  ? parsePositiveInt("limit", cmdOpts.limit)
390
- : getDefaultLimit(format);
638
+ : cmdOpts.verify
639
+ ? 5
640
+ : getDefaultLimit(format);
391
641
  const categories = parseCsvValues(cmdOpts.category);
392
642
  const exclude = parseCsvValues(cmdOpts.exclude);
393
643
 
@@ -435,6 +685,7 @@ function wireSearchCommands(program: Command): void {
435
685
  terminalLinks: await resolveTerminalLinkPolicy(format),
436
686
  });
437
687
  await writeOutput(output, format);
688
+ writeRetrievalTraceReceipt(result.metadata);
438
689
  });
439
690
 
440
691
  // vsearch - Vector similarity search
@@ -547,6 +798,7 @@ function wireSearchCommands(program: Command): void {
547
798
  terminalLinks: await resolveTerminalLinkPolicy(format),
548
799
  });
549
800
  await writeOutput(output, format);
801
+ writeRetrievalTraceReceipt(result.metadata);
550
802
  });
551
803
 
552
804
  // query - Hybrid search with expansion and reranking
@@ -768,6 +1020,7 @@ function wireSearchCommands(program: Command): void {
768
1020
  terminalLinks: await resolveTerminalLinkPolicy(format),
769
1021
  });
770
1022
  await writeOutput(output, format);
1023
+ writeRetrievalTraceReceipt(result.metadata);
771
1024
  });
772
1025
 
773
1026
  // bench - Retrieval benchmark fixture runner
@@ -857,8 +1110,16 @@ function wireSearchCommands(program: Command): void {
857
1110
  )
858
1111
  .option("-C, --candidate-limit <num>", "max candidates passed to reranking")
859
1112
  .option("--answer", "generate short grounded answer")
1113
+ .option(
1114
+ "--verify",
1115
+ "generate and verify every claim against a closed Context Capsule"
1116
+ )
860
1117
  .option("--no-answer", "force retrieval-only output")
861
1118
  .option("--max-answer-tokens <num>", "max answer tokens")
1119
+ .option("--context-budget-tokens <num>", "verified Context token budget")
1120
+ .option("--context-budget-bytes <num>", "verified Context byte budget")
1121
+ .option("--min-score <score>", "minimum retrieval score (0-1)")
1122
+ .option("--graph", "include bounded graph expansion")
862
1123
  .option("--show-sources", "show all retrieved sources (not just cited)")
863
1124
  .option("--json", "JSON output")
864
1125
  .option("--md", "Markdown output")
@@ -889,6 +1150,22 @@ function wireSearchCommands(program: Command): void {
889
1150
  const maxAnswerTokens = cmdOpts.maxAnswerTokens
890
1151
  ? parsePositiveInt("max-answer-tokens", cmdOpts.maxAnswerTokens)
891
1152
  : undefined;
1153
+ const contextBudgetTokens = cmdOpts.contextBudgetTokens
1154
+ ? parsePositiveInt("context-budget-tokens", cmdOpts.contextBudgetTokens)
1155
+ : undefined;
1156
+ const contextBudgetBytes = cmdOpts.contextBudgetBytes
1157
+ ? parsePositiveInt("context-budget-bytes", cmdOpts.contextBudgetBytes)
1158
+ : undefined;
1159
+ const minScore = parseOptionalFloat("min-score", cmdOpts.minScore);
1160
+ if (minScore !== undefined && (minScore < 0 || minScore > 1)) {
1161
+ throw new CliError("VALIDATION", "min-score must be between 0 and 1");
1162
+ }
1163
+ if (cmdOpts.verify && cmdOpts.noAnswer) {
1164
+ throw new CliError(
1165
+ "VALIDATION",
1166
+ "--verify cannot be combined with --no-answer"
1167
+ );
1168
+ }
892
1169
  const categories = parseCsvValues(cmdOpts.category);
893
1170
  const exclude = parseCsvValues(cmdOpts.exclude);
894
1171
 
@@ -937,6 +1214,8 @@ function wireSearchCommands(program: Command): void {
937
1214
  author: cmdOpts.author as string | undefined,
938
1215
  intent: cmdOpts.intent as string | undefined,
939
1216
  exclude,
1217
+ minScore,
1218
+ graph: Boolean(cmdOpts.graph),
940
1219
  queryModes,
941
1220
  noExpand: depthPolicy.noExpand,
942
1221
  noRerank: depthPolicy.noRerank,
@@ -945,7 +1224,10 @@ function wireSearchCommands(program: Command): void {
945
1224
  // Commander creates separate cmdOpts.noAnswer for --no-answer flag
946
1225
  answer: Boolean(cmdOpts.answer),
947
1226
  noAnswer: Boolean(cmdOpts.noAnswer),
1227
+ verify: Boolean(cmdOpts.verify),
948
1228
  maxAnswerTokens,
1229
+ contextBudgetTokens,
1230
+ contextBudgetBytes,
949
1231
  showSources,
950
1232
  json: format === "json",
951
1233
  md: format === "md",
@@ -960,6 +1242,7 @@ function wireSearchCommands(program: Command): void {
960
1242
  showSources,
961
1243
  });
962
1244
  await writeOutput(output, format);
1245
+ writeRetrievalTraceReceipt(result.metadata);
963
1246
  });
964
1247
  }
965
1248
 
@@ -1191,6 +1474,7 @@ function wireRetrievalCommands(program: Command): void {
1191
1474
  parsePositiveInt.bind(null, "limit")
1192
1475
  )
1193
1476
  .option("--line-numbers", "Prefix lines with numbers")
1477
+ .option("--trace-id <id>", "Continue an open retrieval trace")
1194
1478
  .option("--source", "Include source metadata")
1195
1479
  .option("--json", "JSON output")
1196
1480
  .option("--md", "Markdown output")
@@ -1209,6 +1493,7 @@ function wireRetrievalCommands(program: Command): void {
1209
1493
  source: Boolean(cmdOpts.source),
1210
1494
  json: format === "json",
1211
1495
  md: format === "md",
1496
+ traceId: cmdOpts.traceId as string | undefined,
1212
1497
  });
1213
1498
 
1214
1499
  if (!result.success) {
@@ -1225,6 +1510,7 @@ function wireRetrievalCommands(program: Command): void {
1225
1510
  md: format === "md",
1226
1511
  })}\n`
1227
1512
  );
1513
+ writeRetrievalTraceReceipt(result.metadata);
1228
1514
  });
1229
1515
 
1230
1516
  // multi-get - Retrieve multiple documents
@@ -70,3 +70,12 @@ export {
70
70
  ScopeTypeSchema,
71
71
  } from "./types";
72
72
  export type { HttpGatewayConfig } from "./types";
73
+ export {
74
+ RETRIEVAL_TRACE_DEFAULT_RETENTION,
75
+ type RetrievalTraceConfig,
76
+ RetrievalTraceConfigSchema,
77
+ type RetrievalTraceRedactionMode,
78
+ RetrievalTraceRedactionModeSchema,
79
+ type RetrievalTraceRetention,
80
+ RetrievalTraceRetentionSchema,
81
+ } from "./retrieval-traces";
@@ -0,0 +1,56 @@
1
+ /** Opt-in retrieval trace configuration and bounded-retention defaults. */
2
+
3
+ import { z } from "zod";
4
+
5
+ export const RETRIEVAL_TRACE_DEFAULT_RETENTION = {
6
+ maxAgeDays: 30,
7
+ maxTraces: 1_000,
8
+ maxRecordsPerTrace: 10_000,
9
+ maxBytes: 16 * 1024 * 1024,
10
+ } as const;
11
+
12
+ export const RetrievalTraceRetentionSchema = z
13
+ .object({
14
+ maxAgeDays: z.number().int().min(1).max(3_650),
15
+ maxTraces: z.number().int().min(1).max(1_000_000),
16
+ maxRecordsPerTrace: z.number().int().min(1).max(100_000),
17
+ maxBytes: z
18
+ .number()
19
+ .int()
20
+ .min(64 * 1024)
21
+ .max(1024 * 1024 * 1024),
22
+ })
23
+ .strict();
24
+
25
+ export const RetrievalTraceRedactionModeSchema = z.enum(["metadata", "replay"]);
26
+
27
+ const DisabledRetrievalTraceConfigSchema = z
28
+ .object({
29
+ enabled: z.literal(false),
30
+ })
31
+ .strict();
32
+
33
+ const EnabledRetrievalTraceConfigSchema = z
34
+ .object({
35
+ enabled: z.literal(true),
36
+ /**
37
+ * `replay` stores replay inputs such as the raw query and filters. Choosing
38
+ * it is explicit consent; the safer metadata-only projection is recommended.
39
+ */
40
+ redactionMode: RetrievalTraceRedactionModeSchema,
41
+ retention: RetrievalTraceRetentionSchema,
42
+ })
43
+ .strict();
44
+
45
+ export const RetrievalTraceConfigSchema = z.discriminatedUnion("enabled", [
46
+ DisabledRetrievalTraceConfigSchema,
47
+ EnabledRetrievalTraceConfigSchema,
48
+ ]);
49
+
50
+ export type RetrievalTraceConfig = z.infer<typeof RetrievalTraceConfigSchema>;
51
+ export type RetrievalTraceRetention = z.infer<
52
+ typeof RetrievalTraceRetentionSchema
53
+ >;
54
+ export type RetrievalTraceRedactionMode = z.infer<
55
+ typeof RetrievalTraceRedactionModeSchema
56
+ >;
@@ -8,6 +8,7 @@
8
8
  import { z } from "zod";
9
9
 
10
10
  import { URI_PREFIX } from "../app/constants";
11
+ import { RetrievalTraceConfigSchema } from "./retrieval-traces";
11
12
 
12
13
  // ─────────────────────────────────────────────────────────────────────────────
13
14
  // Constants
@@ -338,6 +339,9 @@ export const ConfigSchema = z.object({
338
339
 
339
340
  /** Resident Streamable HTTP MCP gateway configuration */
340
341
  gateway: HttpGatewayConfigSchema.optional(),
342
+
343
+ /** Private local retrieval trace recording. Absent means recording off. */
344
+ retrievalTraces: RetrievalTraceConfigSchema.optional(),
341
345
  });
342
346
 
343
347
  export type Config = Omit<z.infer<typeof ConfigSchema>, "contentTypes"> & {
@@ -6,6 +6,8 @@
6
6
  * canonical Capsule payload, including coverage, omissions, and guidance.
7
7
  */
8
8
 
9
+ import type { FusionSource } from "../pipeline/types";
10
+
9
11
  export const CONTEXT_OMISSION_REASONS = [
10
12
  "duplicate",
11
13
  "overlap",
@@ -43,6 +45,10 @@ export interface MaterializedContextCandidate<
43
45
  text: string;
44
46
  facets: string[];
45
47
  retrievalRank: number;
48
+ /** Absent only for legacy results that predate planner provenance metadata. */
49
+ retrievalSources?: FusionSource[];
50
+ /** Absent only for legacy results that predate planner provenance metadata. */
51
+ graphExpanded?: boolean;
46
52
  value: T;
47
53
  }
48
54
 
@@ -59,10 +59,14 @@ export const contextCapsuleRetrievalSchema = z
59
59
  .object({
60
60
  author: z.string().min(1).max(256).nullable(),
61
61
  lang: z.string().min(1).max(64).nullable(),
62
+ intent: z.string().min(1).max(16_384).nullable().optional(),
63
+ exclude: z.array(nonEmptyText.max(256)).max(128).optional(),
64
+ minScore: z.number().min(0).max(1).nullable().optional(),
62
65
  queryModes: z.array(queryModeSchema).max(128),
63
66
  limit: z.number().int().positive(),
64
67
  candidateLimit: z.number().int().positive(),
65
68
  graphRequested: z.boolean(),
69
+ rerankRequested: z.boolean().optional(),
66
70
  })
67
71
  .strict(),
68
72
  capabilityStates: z