@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
@@ -0,0 +1,228 @@
1
+ /** MCP verified Ask tool over the shared closed-evidence application boundary. */
2
+
3
+ import { z } from "zod";
4
+
5
+ import type { RetrievalTraceSession } from "../../core/retrieval-trace-session";
6
+ import type { QueryModeInput } from "../../pipeline/types";
7
+ import type { ToolContext } from "../server";
8
+ import type { ToolResult } from "./index";
9
+
10
+ import { buildVerifiedAsk } from "../../app/verified-ask";
11
+ import {
12
+ finishRetrievalTraceAfterError,
13
+ retrievalTraceFilters,
14
+ startRetrievalTraceRequest,
15
+ } from "../../core/retrieval-trace-request";
16
+ import { attachRetrievalTraceMetadata } from "../../core/retrieval-trace-session";
17
+ import { normalizeStructuredQueryInput } from "../../core/structured-query";
18
+ import { resolveModelUri } from "../../llm/registry";
19
+ import { answerTraceTerminalStatus } from "../../pipeline/answer";
20
+ import { createMcpModelPorts, type McpModelPortFactory } from "./context";
21
+ import { normalizeTagFilters, runTool } from "./index";
22
+
23
+ const queryModeSchema = z
24
+ .object({
25
+ mode: z.enum(["term", "intent", "hyde"]),
26
+ text: z.string().trim().min(1),
27
+ })
28
+ .strict();
29
+
30
+ export const askInputSchema = z
31
+ .object({
32
+ query: z.string().trim().min(1),
33
+ verify: z.literal(true),
34
+ collection: z.string().optional(),
35
+ limit: z.number().int().min(1).max(100).default(5),
36
+ minScore: z.number().min(0).max(1).optional(),
37
+ lang: z.string().optional(),
38
+ intent: z.string().optional(),
39
+ candidateLimit: z.number().int().min(1).max(100).optional(),
40
+ exclude: z.array(z.string()).optional(),
41
+ queryModes: z.array(queryModeSchema).optional(),
42
+ tagsAll: z.array(z.string()).optional(),
43
+ tagsAny: z.array(z.string()).optional(),
44
+ since: z.string().optional(),
45
+ until: z.string().optional(),
46
+ categories: z.array(z.string()).optional(),
47
+ author: z.string().optional(),
48
+ graph: z.boolean().optional(),
49
+ noGraph: z.boolean().optional(),
50
+ noRerank: z.boolean().optional(),
51
+ maxAnswerTokens: z.number().int().positive().optional(),
52
+ contextBudgetTokens: z.number().int().positive().optional(),
53
+ contextBudgetBytes: z.number().int().positive().optional(),
54
+ })
55
+ .strict();
56
+
57
+ type AskInput = z.infer<typeof askInputSchema>;
58
+
59
+ export interface HandleAskDependencies {
60
+ modelPortFactory?: McpModelPortFactory;
61
+ }
62
+
63
+ const exactSpan = (uri: string, startLine: number, endLine: number): string =>
64
+ `${uri}:L${startLine}${startLine === endLine ? "" : `-L${endLine}`}`;
65
+
66
+ export const formatVerifiedAskReadable = (
67
+ result: Awaited<ReturnType<typeof buildVerifiedAsk>>
68
+ ): string => {
69
+ const numbers = new Map(
70
+ (result.citations ?? []).flatMap((citation, index) =>
71
+ citation.evidenceId ? [[citation.evidenceId, index + 1] as const] : []
72
+ )
73
+ );
74
+ const answer = (result.answer ?? "").replace(
75
+ /\[evidence:([a-f0-9]{64})\]/g,
76
+ (_marker, evidenceId: string) => {
77
+ const number = numbers.get(evidenceId);
78
+ return number === undefined ? "" : `[${number}]`;
79
+ }
80
+ );
81
+ const claims = result.verification?.claims;
82
+ const lines = [
83
+ answer || "No verified answer.",
84
+ "",
85
+ `Verification: ${claims?.answerStatus ?? "unavailable"}`,
86
+ ];
87
+ if (claims?.abstentionReason) {
88
+ lines.push(`Reason: ${claims.abstentionReason}`);
89
+ }
90
+ if (claims) {
91
+ lines.push(
92
+ `Claims: ${claims.coverage.supportedClaims}/${claims.coverage.totalClaims} supported`
93
+ );
94
+ }
95
+ const semantic = result.verification?.semantic;
96
+ if (semantic) {
97
+ lines.push(`Semantic verifier: ${semantic.status} (${semantic.reason})`);
98
+ }
99
+ for (const claim of claims?.claims ?? []) {
100
+ lines.push(`- ${claim.status}: ${claim.text}`);
101
+ for (const evidence of claim.evidence) {
102
+ lines.push(
103
+ ` ${exactSpan(evidence.uri, evidence.startLine, evidence.endLine)} (${evidence.evidenceId})`
104
+ );
105
+ }
106
+ }
107
+ for (const gap of result.verification?.capsule.coverage.gaps ?? []) {
108
+ lines.push(`Gap: ${gap.facet} (${gap.code})`);
109
+ }
110
+ for (const facet of result.verification?.capsule.coverage.unresolvedFacets ??
111
+ []) {
112
+ lines.push(`Unresolved facet: ${facet}`);
113
+ }
114
+ for (const [name, state] of Object.entries(
115
+ result.verification?.capsule.retrieval.capabilityStates ?? {}
116
+ )) {
117
+ if (state.requested && state.outcome !== "used") {
118
+ lines.push(
119
+ `Capability: ${name} ${state.outcome}${state.fallbackReasons.length > 0 ? ` (${state.fallbackReasons.join(", ")})` : ""}`
120
+ );
121
+ }
122
+ }
123
+ for (const [index, citation] of (result.citations ?? []).entries()) {
124
+ const span =
125
+ citation.startLine === undefined || citation.endLine === undefined
126
+ ? citation.uri
127
+ : exactSpan(citation.uri, citation.startLine, citation.endLine);
128
+ lines.push(`[${index + 1}] ${span} (${citation.evidenceId ?? "unknown"})`);
129
+ }
130
+ return lines.join("\n");
131
+ };
132
+
133
+ export const handleAsk = (
134
+ args: AskInput,
135
+ context: ToolContext,
136
+ dependencies: HandleAskDependencies = {}
137
+ ): Promise<ToolResult> =>
138
+ runTool(
139
+ context,
140
+ "gno_ask",
141
+ async () => {
142
+ if (
143
+ args.collection &&
144
+ !context.collections.some(
145
+ (collection) => collection.name === args.collection
146
+ )
147
+ ) {
148
+ throw new Error(`Collection not found: ${args.collection}`);
149
+ }
150
+ const normalized = normalizeStructuredQueryInput(
151
+ args.query,
152
+ (args.queryModes ?? []) as QueryModeInput[]
153
+ );
154
+ if (!normalized.ok) throw new Error(normalized.error.message);
155
+ const query = normalized.value.query;
156
+ const options = {
157
+ ...args,
158
+ queryModes:
159
+ normalized.value.queryModes.length > 0
160
+ ? normalized.value.queryModes
161
+ : undefined,
162
+ tagsAll: normalizeTagFilters(args.tagsAll),
163
+ tagsAny: normalizeTagFilters(args.tagsAny),
164
+ };
165
+ let traceSession: RetrievalTraceSession | undefined;
166
+ let modelPorts: Awaited<ReturnType<typeof createMcpModelPorts>> | null =
167
+ null;
168
+ try {
169
+ const started = await startRetrievalTraceRequest({
170
+ store: context.store,
171
+ config: context.config,
172
+ query,
173
+ filters: retrievalTraceFilters(options),
174
+ pipeline: "ask",
175
+ indexName: context.indexName,
176
+ modelUris: [
177
+ resolveModelUri(
178
+ context.config,
179
+ "embed",
180
+ undefined,
181
+ args.collection
182
+ ),
183
+ resolveModelUri(
184
+ context.config,
185
+ "rerank",
186
+ undefined,
187
+ args.collection
188
+ ),
189
+ resolveModelUri(context.config, "gen", undefined, args.collection),
190
+ ],
191
+ });
192
+ if (!started.ok) throw new Error(started.error.message);
193
+ traceSession = started.value ?? undefined;
194
+ modelPorts = await createMcpModelPorts(
195
+ context,
196
+ args.collection,
197
+ dependencies.modelPortFactory,
198
+ { generation: true }
199
+ );
200
+ if (!modelPorts.genPort) {
201
+ throw new Error(
202
+ "Answer generation requested but no generation model is available"
203
+ );
204
+ }
205
+ const result = await buildVerifiedAsk(query, options, {
206
+ store: context.store,
207
+ config: context.config,
208
+ indexName: context.indexName,
209
+ vectorIndex: modelPorts.vectorIndex,
210
+ embedPort: modelPorts.embedPort,
211
+ rerankPort: modelPorts.rerankPort,
212
+ genPort: modelPorts.genPort,
213
+ traceSession,
214
+ });
215
+ const finished = await traceSession?.finish(
216
+ answerTraceTerminalStatus(result.citations)
217
+ );
218
+ if (finished && !finished.ok) throw new Error(finished.error.message);
219
+ return attachRetrievalTraceMetadata(result, traceSession);
220
+ } catch (error) {
221
+ await finishRetrievalTraceAfterError(traceSession, error);
222
+ throw error;
223
+ } finally {
224
+ await modelPorts?.dispose();
225
+ }
226
+ },
227
+ formatVerifiedAskReadable
228
+ );
@@ -1,7 +1,12 @@
1
1
  /** MCP Context Capsule tools over the shared application runtime. */
2
2
 
3
+ import type { RetrievalTraceSession } from "../../core/retrieval-trace-session";
3
4
  import type { ModelLease } from "../../llm/nodeLlamaCpp/lifecycle";
4
- import type { EmbeddingPort, RerankPort } from "../../llm/types";
5
+ import type {
6
+ EmbeddingPort,
7
+ GenerationPort,
8
+ RerankPort,
9
+ } from "../../llm/types";
5
10
  import type { VectorIndexPort } from "../../store/vector";
6
11
  import type { ToolContext } from "../server";
7
12
  import type { ToolResult } from "./index";
@@ -20,6 +25,10 @@ import {
20
25
  parseContextVerifySurfaceInput,
21
26
  } from "../../app/context-surface";
22
27
  import { createNonTtyProgressRenderer } from "../../cli/progress";
28
+ import {
29
+ finishRetrievalTraceAfterError,
30
+ startRetrievalTraceRequest,
31
+ } from "../../core/retrieval-trace-request";
23
32
  import { LlmAdapter } from "../../llm/nodeLlamaCpp/adapter";
24
33
  import { resolveDownloadPolicy } from "../../llm/policy";
25
34
  import { resolveModelUri } from "../../llm/registry";
@@ -28,11 +37,15 @@ import { createVectorIndexPort } from "../../store/vector";
28
37
  interface ContextToolResultData {
29
38
  structuredContent: Record<string, unknown>;
30
39
  text: string;
40
+ traceId?: string;
31
41
  }
32
42
 
33
43
  const asToolResult = (data: ContextToolResultData): ToolResult => ({
34
44
  content: [{ type: "text", text: data.text }],
35
45
  structuredContent: data.structuredContent,
46
+ ...(data.traceId
47
+ ? { _meta: { gno: { retrievalTrace: { traceId: data.traceId } } } }
48
+ : {}),
36
49
  });
37
50
 
38
51
  const asToolError = (error: unknown): ToolResult => {
@@ -76,13 +89,15 @@ const runContextTool = async (
76
89
 
77
90
  interface McpModelPorts {
78
91
  embedPort: EmbeddingPort | null;
92
+ genPort: GenerationPort | null;
79
93
  rerankPort: RerankPort | null;
80
94
  vectorIndex: VectorIndexPort | null;
81
95
  dispose(): Promise<void>;
82
96
  }
83
97
 
84
- interface McpModelPortFactory {
98
+ export interface McpModelPortFactory {
85
99
  createEmbeddingPort: LlmAdapter["createEmbeddingPort"];
100
+ createGenerationPort?: LlmAdapter["createGenerationPort"];
86
101
  createRerankPort: LlmAdapter["createRerankPort"];
87
102
  acquireModelLease?: LlmAdapter["acquireModelLease"];
88
103
  }
@@ -100,7 +115,8 @@ export const disposeContextModelOwners = async (
100
115
  export const createMcpModelPorts = async (
101
116
  context: ToolContext,
102
117
  collection?: string,
103
- factoryOverride?: McpModelPortFactory
118
+ factoryOverride?: McpModelPortFactory,
119
+ options: { generation?: boolean } = {}
104
120
  ): Promise<McpModelPorts> => {
105
121
  const llm = new LlmAdapter(context.config);
106
122
  const factory = factoryOverride ?? llm;
@@ -116,6 +132,7 @@ export const createMcpModelPorts = async (
116
132
  let embedPort: EmbeddingPort | null = null;
117
133
  let ownedEmbedPort: EmbeddingPort | null = null;
118
134
  let rerankPort: RerankPort | null = null;
135
+ let genPort: GenerationPort | null = null;
119
136
  let vectorIndex: VectorIndexPort | null = null;
120
137
  try {
121
138
  const embedResult = await factory.createEmbeddingPort(embedUri, {
@@ -143,14 +160,26 @@ export const createMcpModelPorts = async (
143
160
  }
144
161
  );
145
162
  if (rerankResult.ok) rerankPort = rerankResult.value;
163
+ if (options.generation && factory.createGenerationPort) {
164
+ const genResult = await factory.createGenerationPort(
165
+ resolveModelUri(context.config, "gen", undefined, collection),
166
+ {
167
+ policy,
168
+ onProgress: (value) => progress("gen", value),
169
+ }
170
+ );
171
+ if (genResult.ok) genPort = genResult.value;
172
+ }
146
173
  return {
147
174
  embedPort,
175
+ genPort,
148
176
  rerankPort,
149
177
  vectorIndex,
150
178
  async dispose() {
151
179
  await disposeContextModelOwners(
152
- [ownedEmbedPort, rerankPort].filter(
153
- (port): port is EmbeddingPort | RerankPort => port !== null
180
+ [ownedEmbedPort, rerankPort, genPort].filter(
181
+ (port): port is EmbeddingPort | RerankPort | GenerationPort =>
182
+ port !== null
154
183
  ),
155
184
  lease
156
185
  );
@@ -158,8 +187,9 @@ export const createMcpModelPorts = async (
158
187
  };
159
188
  } catch (error) {
160
189
  await disposeContextModelOwners(
161
- [ownedEmbedPort, rerankPort].filter(
162
- (port): port is EmbeddingPort | RerankPort => port !== null
190
+ [ownedEmbedPort, rerankPort, genPort].filter(
191
+ (port): port is EmbeddingPort | RerankPort | GenerationPort =>
192
+ port !== null
163
193
  ),
164
194
  lease
165
195
  );
@@ -180,15 +210,50 @@ export const handleContext = (
180
210
  context.config.collections.map((collection) => collection.name)
181
211
  );
182
212
  const useModels = parsed.input.depthPolicy !== "fast";
183
- const modelPorts = useModels
184
- ? await createMcpModelPorts(
185
- context,
186
- parsed.input.collections?.length === 1
187
- ? parsed.input.collections[0]
188
- : undefined
189
- )
190
- : null;
213
+ const collection =
214
+ parsed.input.collections?.length === 1
215
+ ? parsed.input.collections[0]
216
+ : undefined;
217
+ const modelUris = useModels
218
+ ? [
219
+ resolveModelUri(context.config, "embed", undefined, collection),
220
+ resolveModelUri(context.config, "rerank", undefined, collection),
221
+ ]
222
+ : [];
223
+ let modelPorts: Awaited<ReturnType<typeof createMcpModelPorts>> | null =
224
+ null;
225
+ let traceSession: RetrievalTraceSession | undefined;
191
226
  try {
227
+ const traceStart = await startRetrievalTraceRequest({
228
+ store: context.store,
229
+ config: context.config,
230
+ query: parsed.input.query ?? parsed.input.goal,
231
+ goal: parsed.input.goal,
232
+ filters: {
233
+ limit: parsed.input.limit,
234
+ collection,
235
+ collections: [...(parsed.input.collections ?? [])].sort(),
236
+ lang: parsed.input.lang,
237
+ tagsAll: parsed.input.tagsAll,
238
+ tagsAny: parsed.input.tagsAny,
239
+ since: parsed.input.since,
240
+ until: parsed.input.until,
241
+ categories: parsed.input.categories,
242
+ author: parsed.input.author,
243
+ graph: parsed.input.graph,
244
+ candidateLimit: parsed.input.candidateLimit,
245
+ queryModes: parsed.input.queryModes,
246
+ uriPrefix: parsed.input.uriPrefix ?? undefined,
247
+ },
248
+ pipeline: "context",
249
+ indexName: context.indexName,
250
+ modelUris,
251
+ });
252
+ if (!traceStart.ok) throw new Error(traceStart.error.message);
253
+ traceSession = traceStart.value ?? undefined;
254
+ modelPorts = useModels
255
+ ? await createMcpModelPorts(context, collection)
256
+ : null;
192
257
  const capsule = await buildContextCapsule(parsed.input, {
193
258
  store: context.store,
194
259
  config: context.config,
@@ -196,14 +261,21 @@ export const handleContext = (
196
261
  vectorIndex: modelPorts?.vectorIndex ?? null,
197
262
  embedPort: modelPorts?.embedPort ?? null,
198
263
  rerankPort: modelPorts?.rerankPort ?? null,
264
+ traceSession,
199
265
  });
266
+ const finalized = await traceSession?.finish("completed");
267
+ if (finalized && !finalized.ok) throw new Error(finalized.error.message);
200
268
  return {
201
269
  structuredContent: capsule as unknown as Record<string, unknown>,
202
270
  // MCP model context receives this projection exactly once. The full
203
271
  // canonical capsule remains available to application clients through
204
272
  // structuredContent and is deliberately not duplicated in text.
205
273
  text: formatContextCapsuleAgentJson(capsule),
274
+ traceId: traceSession?.metadata()?.traceId,
206
275
  };
276
+ } catch (cause) {
277
+ await finishRetrievalTraceAfterError(traceSession, cause);
278
+ throw cause;
207
279
  } finally {
208
280
  await modelPorts?.dispose();
209
281
  }
@@ -16,6 +16,11 @@ import {
16
16
  } from "../../core/document-capabilities";
17
17
  import { resolveEffectiveIndex } from "../../core/indexed-reference";
18
18
  import { parseRef } from "../../core/ref-parser";
19
+ import {
20
+ attachRetrievalTraceMetadata,
21
+ evidenceFromExactDocument,
22
+ RetrievalTraceSession,
23
+ } from "../../core/retrieval-trace-session";
19
24
  import { openScopedIndexStore } from "../../store/sqlite/scoped-index";
20
25
  import { runTool, type ToolResult } from "./index";
21
26
 
@@ -24,6 +29,7 @@ interface GetInput {
24
29
  fromLine?: number;
25
30
  lineCount?: number;
26
31
  lineNumbers?: boolean;
32
+ traceId?: string;
27
33
  }
28
34
 
29
35
  interface GetResponse {
@@ -165,6 +171,7 @@ export function handleGet(
165
171
 
166
172
  // Apply line range if specified
167
173
  let content = fullContent;
174
+ let exactContent = fullContent;
168
175
  let returnedLines: { start: number; end: number } | undefined;
169
176
 
170
177
  // lineNumbers defaults to true per spec
@@ -176,12 +183,14 @@ export function handleGet(
176
183
  if (startLine > totalLines) {
177
184
  // Return empty content for out-of-range request
178
185
  content = "";
186
+ exactContent = "";
179
187
  returnedLines = undefined;
180
188
  } else {
181
189
  const count = args.lineCount ?? totalLines - startLine + 1;
182
190
  const endLine = Math.min(startLine + count - 1, totalLines);
183
191
 
184
192
  const slicedLines = contentLines.slice(startLine - 1, endLine);
193
+ exactContent = slicedLines.join("\n");
185
194
 
186
195
  if (showLineNumbers) {
187
196
  content = slicedLines
@@ -242,7 +251,32 @@ export function handleGet(
242
251
  }),
243
252
  };
244
253
 
245
- return response;
254
+ if (!args.traceId) return response;
255
+ const resumed = await RetrievalTraceSession.resume({
256
+ store: scoped.store,
257
+ config: ctx.config.retrievalTraces,
258
+ traceId: args.traceId,
259
+ });
260
+ if (!resumed.ok) throw new Error(resumed.error.message);
261
+ const traceSession = resumed.value;
262
+ if (!traceSession) return response;
263
+ const lines = returnedLines ?? { start: 1, end: totalLines };
264
+ const evidence = evidenceFromExactDocument({
265
+ docid: response.docid,
266
+ uri: response.uri,
267
+ sourceHash: response.source.sourceHash,
268
+ mirrorHash: response.conversion?.mirrorHash,
269
+ content: exactContent,
270
+ startLine: lines.start,
271
+ endLine: lines.end,
272
+ });
273
+ if (evidence) {
274
+ const got = await traceSession.recordEvidence("get", [evidence]);
275
+ if (!got.ok) throw new Error(got.error.message);
276
+ const opened = await traceSession.recordEvidence("open", [evidence]);
277
+ if (!opened.ok) throw new Error(opened.error.message);
278
+ }
279
+ return attachRetrievalTraceMetadata(response, traceSession);
246
280
  } finally {
247
281
  await scoped.close();
248
282
  }
@@ -16,8 +16,10 @@ import {
16
16
  } from "../../app/context-surface";
17
17
  import { CAPTURE_MAX_TEXT_BYTES } from "../../core/capture";
18
18
  import { NOTE_PRESETS, type NotePresetId } from "../../core/note-presets";
19
+ import { RETRIEVAL_TRACE_METADATA } from "../../core/retrieval-trace-session";
19
20
  import { normalizeTag } from "../../core/tags";
20
21
  import { handleAddCollection } from "./add-collection";
22
+ import { askInputSchema, handleAsk } from "./ask";
21
23
  import { handleCapture } from "./capture";
22
24
  import { handleClearCollectionEmbeddings } from "./clear-collection-embeddings";
23
25
  import { handleContext, handleContextVerify } from "./context";
@@ -42,6 +44,20 @@ import { handleRemoveCollection } from "./remove-collection";
42
44
  import { handleSearch } from "./search";
43
45
  import { handleStatus } from "./status";
44
46
  import { handleSync } from "./sync";
47
+ import {
48
+ handleTraceDelete,
49
+ handleTraceExport,
50
+ handleTraceLabel,
51
+ handleTraceList,
52
+ handleTracePurge,
53
+ handleTraceShow,
54
+ traceDeleteInputSchema,
55
+ traceExportInputSchema,
56
+ traceLabelInputSchema,
57
+ traceListInputSchema,
58
+ tracePurgeInputSchema,
59
+ traceShowInputSchema,
60
+ } from "./trace";
45
61
  import { handleVsearch } from "./vsearch";
46
62
  import {
47
63
  handleCreateFolder,
@@ -81,6 +97,7 @@ export const MCP_TOOL_DESCRIPTIONS = {
81
97
  "Compile one deterministic, budgeted, extractive evidence Capsule with exact line spans, coverage gaps, omissions, provenance, and verification fingerprints. Raw search/get tools remain available for manual retrieval.",
82
98
  contextVerify:
83
99
  "Verify a saved Context Capsule without rebuilding or mutating it. Reports unchanged, stale, missing, reranked, and fingerprint drift states against the active index.",
100
+ ask: "Generate one answer from a deterministic Context Capsule, verify every substantive claim against exact retained spans, and abstain unless support coverage is complete. Read-only; returns the Capsule, freshness receipt, claim verdicts, gaps, and evidence IDs.",
84
101
  } as const;
85
102
 
86
103
  /** Tool names whose execution mutates disk, config, or index state. */
@@ -96,6 +113,10 @@ export const MCP_WRITE_TOOL_NAMES = new Set([
96
113
  "gno_rename_note",
97
114
  "gno_move_note",
98
115
  "gno_duplicate_note",
116
+ "gno_trace_label",
117
+ "gno_trace_export",
118
+ "gno_trace_delete",
119
+ "gno_trace_purge",
99
120
  ]);
100
121
 
101
122
  // ─────────────────────────────────────────────────────────────────────────────
@@ -563,6 +584,12 @@ const getInputSchema = z.object({
563
584
  .boolean()
564
585
  .default(true)
565
586
  .describe("Include line numbers in output"),
587
+ traceId: z
588
+ .string()
589
+ .min(1)
590
+ .max(128)
591
+ .optional()
592
+ .describe("Continue an open retrieval trace returned by a search/query"),
566
593
  });
567
594
 
568
595
  const multiGetInputSchema = z.object({
@@ -847,9 +874,16 @@ export async function runTool<T>(
847
874
  const modelLease = ctx.acquireModelLease?.();
848
875
  try {
849
876
  const data = await (ctx.runWithSnapshot?.(fn) ?? fn());
877
+ const traceMetadata =
878
+ data !== null && typeof data === "object"
879
+ ? (data as Record<PropertyKey, unknown>)[RETRIEVAL_TRACE_METADATA]
880
+ : undefined;
850
881
  return {
851
882
  content: [{ type: "text", text: formatText(data) }],
852
883
  structuredContent: data as { [x: string]: unknown },
884
+ ...(traceMetadata
885
+ ? { _meta: { gno: { retrievalTrace: traceMetadata } } }
886
+ : {}),
853
887
  };
854
888
  } catch (e) {
855
889
  // Exception firewall: never throw, always return isError
@@ -946,6 +980,13 @@ export function registerTools(server: McpServer, ctx: ToolContext): void {
946
980
  (args) => handleContextVerify(args, ctx)
947
981
  );
948
982
 
983
+ server.tool(
984
+ "gno_ask",
985
+ MCP_TOOL_DESCRIPTIONS.ask,
986
+ askInputSchema.shape,
987
+ (args) => handleAsk(args, ctx)
988
+ );
989
+
949
990
  server.tool(
950
991
  "gno_search",
951
992
  MCP_TOOL_DESCRIPTIONS.search,
@@ -995,6 +1036,20 @@ export function registerTools(server: McpServer, ctx: ToolContext): void {
995
1036
  (args) => handleStatus(args, ctx)
996
1037
  );
997
1038
 
1039
+ server.tool(
1040
+ "gno_trace_list",
1041
+ "List bounded metadata-only summaries of private local retrieval traces. Raw replay queries are omitted from history.",
1042
+ traceListInputSchema.shape,
1043
+ (args) => handleTraceList(args, ctx)
1044
+ );
1045
+
1046
+ server.tool(
1047
+ "gno_trace_show",
1048
+ "Inspect one bounded local retrieval trace. Replay-mode content is returned only for the explicitly requested trace.",
1049
+ traceShowInputSchema.shape,
1050
+ (args) => handleTraceShow(args, ctx)
1051
+ );
1052
+
998
1053
  server.tool(
999
1054
  "gno_list_tags",
1000
1055
  "List all tags with document counts. Use prefix to filter hierarchical tags (e.g. 'project/').",
@@ -1052,6 +1107,34 @@ export function registerTools(server: McpServer, ctx: ToolContext): void {
1052
1107
  );
1053
1108
 
1054
1109
  if (ctx.enableWrite) {
1110
+ server.tool(
1111
+ "gno_trace_label",
1112
+ "Append an explicit relevant, irrelevant, or missing_expected judgment to a local retrieval trace.",
1113
+ traceLabelInputSchema.shape,
1114
+ (args) => handleTraceLabel(args, ctx)
1115
+ );
1116
+
1117
+ server.tool(
1118
+ "gno_trace_export",
1119
+ "Build a deterministic local agentic receipt from one or more immutable terminal retrieval traces.",
1120
+ traceExportInputSchema.shape,
1121
+ (args) => handleTraceExport(args, ctx)
1122
+ );
1123
+
1124
+ server.tool(
1125
+ "gno_trace_delete",
1126
+ "Delete one private local retrieval trace and all owned receipt records.",
1127
+ traceDeleteInputSchema.shape,
1128
+ (args) => handleTraceDelete(args, ctx)
1129
+ );
1130
+
1131
+ server.tool(
1132
+ "gno_trace_purge",
1133
+ "Purge every private local retrieval trace receipt. Requires confirm=true.",
1134
+ tracePurgeInputSchema.shape,
1135
+ (args) => handleTracePurge(args, ctx)
1136
+ );
1137
+
1055
1138
  server.tool(
1056
1139
  "gno_capture",
1057
1140
  "Create a new document in a collection. Writes to disk. Does NOT auto-embed; run gno_index after to make it searchable via vector search.",