@gmickel/gno 1.17.0 → 1.19.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 (167) hide show
  1. package/README.md +18 -4
  2. package/assets/skill/SKILL.md +44 -1
  3. package/assets/skill/mcp-reference.md +21 -0
  4. package/package.json +3 -2
  5. package/spec/AGENTS.md +83 -0
  6. package/spec/CLAUDE.md +83 -0
  7. package/spec/bench-fixture.schema.json +137 -0
  8. package/spec/cli.md +2894 -0
  9. package/spec/db/schema.sql +442 -0
  10. package/spec/evals-agentic.md +510 -0
  11. package/spec/evals.md +1106 -0
  12. package/spec/mcp.md +2229 -0
  13. package/spec/output-schemas/activation-verification.schema.json +515 -0
  14. package/spec/output-schemas/ask.schema.json +366 -0
  15. package/spec/output-schemas/backlinks.schema.json +131 -0
  16. package/spec/output-schemas/bench-result.schema.json +120 -0
  17. package/spec/output-schemas/capture-receipt.schema.json +143 -0
  18. package/spec/output-schemas/collection-list.schema.json +45 -0
  19. package/spec/output-schemas/context-capsule-v1.schema.json +691 -0
  20. package/spec/output-schemas/context-capsule-verification.schema.json +1338 -0
  21. package/spec/output-schemas/context-list.schema.json +21 -0
  22. package/spec/output-schemas/doctor.schema.json +313 -0
  23. package/spec/output-schemas/error.schema.json +30 -0
  24. package/spec/output-schemas/expansion.schema.json +37 -0
  25. package/spec/output-schemas/get.schema.json +140 -0
  26. package/spec/output-schemas/graph-query.schema.json +99 -0
  27. package/spec/output-schemas/graph.schema.json +371 -0
  28. package/spec/output-schemas/links-list.schema.json +186 -0
  29. package/spec/output-schemas/mcp-add-collection-result.schema.json +23 -0
  30. package/spec/output-schemas/mcp-capture-result.schema.json +152 -0
  31. package/spec/output-schemas/mcp-http-error.schema.json +30 -0
  32. package/spec/output-schemas/mcp-job-list.schema.json +58 -0
  33. package/spec/output-schemas/mcp-job-status.schema.json +224 -0
  34. package/spec/output-schemas/mcp-remove-result.schema.json +39 -0
  35. package/spec/output-schemas/mcp-sync-result.schema.json +41 -0
  36. package/spec/output-schemas/mcp-tag-result.schema.json +33 -0
  37. package/spec/output-schemas/models-list.schema.json +93 -0
  38. package/spec/output-schemas/multi-get.schema.json +103 -0
  39. package/spec/output-schemas/process-status.schema.json +119 -0
  40. package/spec/output-schemas/query-diagnose.schema.json +123 -0
  41. package/spec/output-schemas/resident-status.schema.json +154 -0
  42. package/spec/output-schemas/retrieval-trace-common.schema.json +492 -0
  43. package/spec/output-schemas/retrieval-trace-delete.schema.json +16 -0
  44. package/spec/output-schemas/retrieval-trace-export.schema.json +61 -0
  45. package/spec/output-schemas/retrieval-trace-filters.schema.json +139 -0
  46. package/spec/output-schemas/retrieval-trace-judgment.schema.json +15 -0
  47. package/spec/output-schemas/retrieval-trace-list.schema.json +18 -0
  48. package/spec/output-schemas/retrieval-trace-payloads.schema.json +178 -0
  49. package/spec/output-schemas/retrieval-trace-purge.schema.json +31 -0
  50. package/spec/output-schemas/retrieval-trace-qrels.schema.json +303 -0
  51. package/spec/output-schemas/retrieval-trace-replay.schema.json +286 -0
  52. package/spec/output-schemas/retrieval-trace-show.schema.json +69 -0
  53. package/spec/output-schemas/retrieval-trace-summary.schema.json +65 -0
  54. package/spec/output-schemas/search-result.schema.json +154 -0
  55. package/spec/output-schemas/search-results.schema.json +338 -0
  56. package/spec/output-schemas/similar.schema.json +84 -0
  57. package/spec/output-schemas/status.schema.json +676 -0
  58. package/spec/output-schemas/tags-list.schema.json +48 -0
  59. package/src/app/context-runtime-types.ts +3 -0
  60. package/src/app/context-runtime.ts +15 -1
  61. package/src/cli/commands/ask.ts +106 -36
  62. package/src/cli/commands/context-build.ts +56 -9
  63. package/src/cli/commands/daemon.ts +69 -2
  64. package/src/cli/commands/get.ts +64 -3
  65. package/src/cli/commands/models/pull.ts +13 -3
  66. package/src/cli/commands/query.ts +62 -23
  67. package/src/cli/commands/replay.ts +140 -0
  68. package/src/cli/commands/search.ts +48 -3
  69. package/src/cli/commands/shared.ts +3 -1
  70. package/src/cli/commands/status.ts +2 -0
  71. package/src/cli/commands/trace.ts +200 -0
  72. package/src/cli/commands/vsearch.ts +75 -53
  73. package/src/cli/detach.ts +37 -20
  74. package/src/cli/program.ts +329 -27
  75. package/src/config/index.ts +12 -0
  76. package/src/config/retrieval-traces.ts +56 -0
  77. package/src/config/types.ts +41 -0
  78. package/src/core/context-compiler.ts +11 -4
  79. package/src/core/job-manager.ts +19 -0
  80. package/src/core/mutation-generations.ts +33 -0
  81. package/src/core/retrieval-qrels.ts +405 -0
  82. package/src/core/retrieval-replay-candidate.ts +368 -0
  83. package/src/core/retrieval-replay-types.ts +109 -0
  84. package/src/core/retrieval-replay-validation.ts +89 -0
  85. package/src/core/retrieval-replay.ts +441 -0
  86. package/src/core/retrieval-trace-evidence-origin.ts +175 -0
  87. package/src/core/retrieval-trace-export.ts +113 -0
  88. package/src/core/retrieval-trace-filter-normalization.ts +27 -0
  89. package/src/core/retrieval-trace-filters.ts +19 -0
  90. package/src/core/retrieval-trace-management-helpers.ts +247 -0
  91. package/src/core/retrieval-trace-management-types.ts +132 -0
  92. package/src/core/retrieval-trace-management.ts +422 -0
  93. package/src/core/retrieval-trace-request.ts +141 -0
  94. package/src/core/retrieval-trace-session.ts +494 -0
  95. package/src/core/retrieval-trace.ts +472 -0
  96. package/src/llm/cache.ts +13 -3
  97. package/src/llm/nodeLlamaCpp/adapter.ts +10 -1
  98. package/src/llm/nodeLlamaCpp/lifecycle.ts +71 -0
  99. package/src/mcp/context.ts +161 -0
  100. package/src/mcp/http-security.ts +477 -0
  101. package/src/mcp/http-session.ts +272 -0
  102. package/src/mcp/http-transport.ts +370 -0
  103. package/src/mcp/resources/index.ts +141 -134
  104. package/src/mcp/server.ts +19 -79
  105. package/src/mcp/tools/add-collection.ts +3 -1
  106. package/src/mcp/tools/capture.ts +3 -0
  107. package/src/mcp/tools/clear-collection-embeddings.ts +2 -0
  108. package/src/mcp/tools/context.ts +68 -16
  109. package/src/mcp/tools/embed.ts +62 -52
  110. package/src/mcp/tools/get.ts +35 -1
  111. package/src/mcp/tools/index-cmd.ts +88 -74
  112. package/src/mcp/tools/index.ts +96 -2
  113. package/src/mcp/tools/query.ts +95 -64
  114. package/src/mcp/tools/remove-collection.ts +2 -0
  115. package/src/mcp/tools/search.ts +36 -13
  116. package/src/mcp/tools/status.ts +11 -0
  117. package/src/mcp/tools/sync.ts +16 -14
  118. package/src/mcp/tools/trace.ts +143 -0
  119. package/src/mcp/tools/vsearch.ts +71 -38
  120. package/src/mcp/tools/workspace-write.ts +7 -3
  121. package/src/pipeline/answer.ts +167 -26
  122. package/src/pipeline/graph-retrieval.ts +15 -1
  123. package/src/pipeline/hybrid.ts +151 -43
  124. package/src/pipeline/search.ts +36 -3
  125. package/src/pipeline/trace-metadata.ts +47 -0
  126. package/src/pipeline/types.ts +43 -0
  127. package/src/pipeline/vsearch.ts +101 -38
  128. package/src/sdk/client.ts +380 -71
  129. package/src/sdk/documents.ts +48 -1
  130. package/src/sdk/index.ts +17 -0
  131. package/src/sdk/types.ts +28 -0
  132. package/src/serve/background-runtime.ts +12 -212
  133. package/src/serve/context-capsule.ts +67 -8
  134. package/src/serve/embed-scheduler.ts +74 -43
  135. package/src/serve/index.ts +9 -0
  136. package/src/serve/jobs.ts +78 -80
  137. package/src/serve/public/app.tsx +12 -1
  138. package/src/serve/public/components/HealthCenter.tsx +74 -1
  139. package/src/serve/public/globals.built.css +1 -1
  140. package/src/serve/public/lib/workspace-tabs.ts +2 -0
  141. package/src/serve/public/pages/Dashboard.tsx +11 -0
  142. package/src/serve/public/pages/TraceHistory.tsx +478 -0
  143. package/src/serve/public/pages/trace-history-detail.tsx +224 -0
  144. package/src/serve/resident-admission.ts +159 -0
  145. package/src/serve/resident-background-work.ts +39 -0
  146. package/src/serve/resident-request.ts +55 -0
  147. package/src/serve/resident-runtime.ts +490 -0
  148. package/src/serve/resident-status.ts +96 -0
  149. package/src/serve/retrieval-trace.ts +28 -0
  150. package/src/serve/routes/api.ts +629 -239
  151. package/src/serve/routes/mcp.ts +69 -0
  152. package/src/serve/routes/traces.ts +156 -0
  153. package/src/serve/server.ts +276 -37
  154. package/src/serve/status-model.ts +51 -0
  155. package/src/serve/status.ts +5 -0
  156. package/src/store/index.ts +31 -0
  157. package/src/store/migrations/014-retrieval-traces.ts +303 -0
  158. package/src/store/migrations/index.ts +2 -0
  159. package/src/store/retrieval-trace-codec.ts +384 -0
  160. package/src/store/sqlite/adapter.ts +179 -10
  161. package/src/store/sqlite/retrieval-trace-management-store.ts +341 -0
  162. package/src/store/sqlite/retrieval-trace-retention.ts +349 -0
  163. package/src/store/sqlite/retrieval-trace-rows.ts +267 -0
  164. package/src/store/sqlite/retrieval-trace-store.ts +515 -0
  165. package/src/store/types.ts +297 -0
  166. package/src/store/vector/sqlite-vec.ts +76 -1
  167. package/src/store/vector/types.ts +1 -1
@@ -0,0 +1,48 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "gno://schemas/tags-list@1.0",
4
+ "title": "GNO Tags List",
5
+ "description": "List of tags with document counts",
6
+ "type": "object",
7
+ "required": ["tags", "meta"],
8
+ "properties": {
9
+ "tags": {
10
+ "type": "array",
11
+ "description": "List of tags with counts",
12
+ "items": {
13
+ "type": "object",
14
+ "required": ["tag", "count"],
15
+ "properties": {
16
+ "tag": {
17
+ "type": "string",
18
+ "description": "Tag name (lowercase, alphanumeric with hyphens/dots/slashes)"
19
+ },
20
+ "count": {
21
+ "type": "integer",
22
+ "description": "Number of documents with this tag",
23
+ "minimum": 1
24
+ }
25
+ }
26
+ }
27
+ },
28
+ "meta": {
29
+ "type": "object",
30
+ "required": ["totalTags"],
31
+ "properties": {
32
+ "totalTags": {
33
+ "type": "integer",
34
+ "description": "Total number of unique tags",
35
+ "minimum": 0
36
+ },
37
+ "collection": {
38
+ "type": "string",
39
+ "description": "Collection filter applied"
40
+ },
41
+ "prefix": {
42
+ "type": "string",
43
+ "description": "Prefix filter applied"
44
+ }
45
+ }
46
+ }
47
+ }
48
+ }
@@ -2,6 +2,7 @@ import type { Config } from "../config/types";
2
2
  import type { ContextCapsuleV1 } from "../core/context-capsule";
3
3
  import type { ContextEvidenceCompilerDeps } from "../core/context-evidence";
4
4
  import type { ContextVerifierDeps } from "../core/context-verifier";
5
+ import type { RetrievalTraceSession } from "../core/retrieval-trace-session";
5
6
  import type { EmbeddingPort, RerankPort } from "../llm/types";
6
7
  import type { QueryModeInput } from "../pipeline/types";
7
8
  import type { StorePort } from "../store/types";
@@ -45,6 +46,8 @@ export interface ContextCapsuleRuntimeDeps {
45
46
  countTokens?: (accountingJson: string) => number;
46
47
  tokenizerFingerprint?: string | null;
47
48
  resolveCurrentRanks?: ContextVerifierDeps["resolveCurrentRanks"];
49
+ /** Optional non-canonical receipt session owned by the calling surface. */
50
+ traceSession?: RetrievalTraceSession;
48
51
  }
49
52
 
50
53
  export type ContextRuntimeErrorCode =
@@ -89,7 +89,11 @@ export const buildContextCapsule = async (
89
89
  rerankPort: requestNoRerank ? null : (deps.rerankPort ?? null),
90
90
  },
91
91
  request.query,
92
- { ...request, noRerank: requestNoRerank }
92
+ {
93
+ ...request,
94
+ noRerank: requestNoRerank,
95
+ traceSession: deps.traceSession,
96
+ }
93
97
  );
94
98
  if (!result.ok) {
95
99
  throw new ContextRuntimeError(
@@ -115,6 +119,16 @@ export const buildContextCapsule = async (
115
119
  : "No in-scope evidence was available for the Context Capsule"
116
120
  );
117
121
  }
122
+ const traceResult = await deps.traceSession?.recordContext(
123
+ plan.projection.value
124
+ );
125
+ if (traceResult && !traceResult.ok) {
126
+ throw new ContextRuntimeError(
127
+ "retrieval_failed",
128
+ `Trace recording failed: ${traceResult.error.message}`,
129
+ traceResult.error.cause
130
+ );
131
+ }
118
132
  return plan.projection.value;
119
133
  };
120
134
 
@@ -5,6 +5,8 @@
5
5
  * @module src/cli/commands/ask
6
6
  */
7
7
 
8
+ import type { RetrievalTraceSurfaceMetadata } from "../../core/retrieval-trace-session";
9
+ import type { RetrievalTraceSession } from "../../core/retrieval-trace-session";
8
10
  import type {
9
11
  EmbeddingPort,
10
12
  GenerationPort,
@@ -12,12 +14,18 @@ import type {
12
14
  } from "../../llm/types";
13
15
  import type { AskOptions, AskResult, Citation } from "../../pipeline/types";
14
16
 
17
+ import {
18
+ finishRetrievalTraceAfterError,
19
+ retrievalTraceFilters,
20
+ startRetrievalTraceRequest,
21
+ } from "../../core/retrieval-trace-request";
15
22
  import { LlmAdapter } from "../../llm/nodeLlamaCpp/adapter";
16
23
  import { resolveDownloadPolicy } from "../../llm/policy";
17
24
  import { resolveModelUri } from "../../llm/registry";
18
25
  import {
26
+ answerTraceTerminalStatus,
19
27
  generateGroundedAnswer,
20
- processAnswerResult,
28
+ processAnswerResultWithTrace,
21
29
  } from "../../pipeline/answer";
22
30
  import { type HybridSearchDeps, searchHybrid } from "../../pipeline/hybrid";
23
31
  import {
@@ -55,7 +63,11 @@ export type AskCommandOptions = AskOptions & {
55
63
  };
56
64
 
57
65
  export type AskCommandResult =
58
- | { success: true; data: AskResult }
66
+ | {
67
+ success: true;
68
+ data: AskResult;
69
+ metadata?: RetrievalTraceSurfaceMetadata;
70
+ }
59
71
  | { success: false; error: string };
60
72
 
61
73
  // ─────────────────────────────────────────────────────────────────────────────
@@ -88,8 +100,50 @@ export async function ask(
88
100
  let expandPort: GenerationPort | null = null;
89
101
  let answerPort: GenerationPort | null = null;
90
102
  let rerankPort: RerankPort | null = null;
103
+ let traceSession: RetrievalTraceSession | undefined;
91
104
 
92
105
  try {
106
+ const answerRequested = Boolean(options.answer && !options.noAnswer);
107
+ const embedUri = resolveModelUri(
108
+ config,
109
+ "embed",
110
+ options.embedModel,
111
+ options.collection
112
+ );
113
+ const expandUri =
114
+ !options.noExpand && !options.queryModes?.length
115
+ ? resolveModelUri(
116
+ config,
117
+ "expand",
118
+ options.expandModel ?? options.genModel,
119
+ options.collection
120
+ )
121
+ : undefined;
122
+ const answerUri = answerRequested
123
+ ? resolveModelUri(config, "gen", options.genModel, options.collection)
124
+ : undefined;
125
+ const rerankUri = !options.noRerank
126
+ ? resolveModelUri(
127
+ config,
128
+ "rerank",
129
+ options.rerankModel,
130
+ options.collection
131
+ )
132
+ : undefined;
133
+ const traceStart = await startRetrievalTraceRequest({
134
+ store,
135
+ config,
136
+ query,
137
+ filters: retrievalTraceFilters({ ...options, limit }),
138
+ pipeline: "ask",
139
+ modelUris: [embedUri, expandUri, answerUri, rerankUri].filter(
140
+ (value): value is string => Boolean(value)
141
+ ),
142
+ });
143
+ if (!traceStart.ok) {
144
+ return { success: false, error: traceStart.error.message };
145
+ }
146
+ traceSession = traceStart.value ?? undefined;
93
147
  const llm = new LlmAdapter(config);
94
148
 
95
149
  // Resolve download policy from env/flags
@@ -105,12 +159,6 @@ export async function ask(
105
159
  : undefined;
106
160
 
107
161
  // Create embedding port
108
- const embedUri = resolveModelUri(
109
- config,
110
- "embed",
111
- options.embedModel,
112
- options.collection
113
- );
114
162
  const embedResult = await llm.createEmbeddingPort(embedUri, {
115
163
  policy,
116
164
  onProgress: downloadProgress
@@ -122,13 +170,7 @@ export async function ask(
122
170
  }
123
171
 
124
172
  // Create expansion port when expansion is enabled.
125
- if (!options.noExpand && !options.queryModes?.length) {
126
- const expandUri = resolveModelUri(
127
- config,
128
- "expand",
129
- options.expandModel ?? options.genModel,
130
- options.collection
131
- );
173
+ if (expandUri) {
132
174
  const genResult = await llm.createExpansionPort(expandUri, {
133
175
  policy,
134
176
  onProgress: downloadProgress
@@ -141,14 +183,8 @@ export async function ask(
141
183
  }
142
184
 
143
185
  // Create answer generation port when answers are requested.
144
- if (options.answer) {
145
- const genUri = resolveModelUri(
146
- config,
147
- "gen",
148
- options.genModel,
149
- options.collection
150
- );
151
- const genResult = await llm.createGenerationPort(genUri, {
186
+ if (answerUri) {
187
+ const genResult = await llm.createGenerationPort(answerUri, {
152
188
  policy,
153
189
  onProgress: downloadProgress
154
190
  ? (progress) => downloadProgress("gen", progress)
@@ -160,13 +196,7 @@ export async function ask(
160
196
  }
161
197
 
162
198
  // Create rerank port (unless --fast or --no-rerank)
163
- if (!options.noRerank) {
164
- const rerankUri = resolveModelUri(
165
- config,
166
- "rerank",
167
- options.rerankModel,
168
- options.collection
169
- );
199
+ if (rerankUri) {
170
200
  const rerankResult = await llm.createRerankPort(rerankUri, {
171
201
  policy,
172
202
  onProgress: downloadProgress
@@ -208,12 +238,14 @@ export async function ask(
208
238
  expandPort,
209
239
  rerankPort,
210
240
  };
211
-
212
- // Check if answer generation is explicitly requested
213
- const answerRequested = options.answer && !options.noAnswer;
214
-
215
241
  // Fail early if --answer is requested but no generation model available
216
242
  if (answerRequested && answerPort === null) {
243
+ await traceSession?.recordCapability(
244
+ "answer_generation",
245
+ "unavailable",
246
+ "model_unavailable"
247
+ );
248
+ await traceSession?.finish("failed");
217
249
  return {
218
250
  success: false,
219
251
  error:
@@ -239,9 +271,11 @@ export async function ask(
239
271
  noExpand: options.noExpand,
240
272
  noRerank: options.noRerank,
241
273
  candidateLimit: options.candidateLimit,
274
+ traceSession,
242
275
  });
243
276
 
244
277
  if (!searchResult.ok) {
278
+ await traceSession?.finish("failed");
245
279
  return { success: false, error: searchResult.error.message };
246
280
  }
247
281
 
@@ -261,6 +295,7 @@ export async function ask(
261
295
  answerRequested && answerPort !== null && results.length > 0;
262
296
 
263
297
  if (shouldGenerateAnswer && answerPort) {
298
+ await traceSession?.recordCapability("answer_generation", "attempted");
264
299
  const maxTokens = options.maxAnswerTokens ?? 512;
265
300
  const rawResult = await generateGroundedAnswer(
266
301
  { genPort: answerPort, store },
@@ -271,15 +306,25 @@ export async function ask(
271
306
 
272
307
  // Fail loudly if generation was requested but failed
273
308
  if (!rawResult) {
309
+ await traceSession?.recordCapability(
310
+ "answer_generation",
311
+ "failed",
312
+ "generation_failed"
313
+ );
314
+ await traceSession?.finish("failed");
274
315
  return {
275
316
  success: false,
276
317
  error:
277
318
  "Answer generation failed. The generation model may have encountered an error.",
278
319
  };
279
320
  }
321
+ await traceSession?.recordCapability("answer_generation", "used");
280
322
 
281
323
  // Process answer: extract valid citations, filter, renumber
282
- const processed = processAnswerResult(rawResult);
324
+ const processed = await processAnswerResultWithTrace(
325
+ rawResult,
326
+ traceSession
327
+ );
283
328
  answer = processed.answer;
284
329
  citations = processed.citations;
285
330
  answerContext = processed.answerContext;
@@ -307,7 +352,32 @@ export async function ask(
307
352
  },
308
353
  };
309
354
 
310
- return { success: true, data: askResult };
355
+ if (answerRequested) {
356
+ if (!answerGenerated) {
357
+ await traceSession?.recordCapability(
358
+ "answer_generation",
359
+ "unavailable",
360
+ "no_evidence"
361
+ );
362
+ }
363
+ const finalized = await traceSession?.finish(
364
+ answerTraceTerminalStatus(citations)
365
+ );
366
+ if (finalized && !finalized.ok) {
367
+ return { success: false, error: finalized.error.message };
368
+ }
369
+ }
370
+ return {
371
+ success: true,
372
+ data: askResult,
373
+ metadata: traceSession?.metadata(),
374
+ };
375
+ } catch (cause) {
376
+ await finishRetrievalTraceAfterError(traceSession, cause);
377
+ return {
378
+ success: false,
379
+ error: cause instanceof Error ? cause.message : "Ask failed",
380
+ };
311
381
  } finally {
312
382
  if (embedPort) {
313
383
  await embedPort.dispose();
@@ -1,6 +1,7 @@
1
1
  /** Context Capsule build command over the shared application runtime. */
2
2
 
3
3
  import type { ContextCapsuleBuildInput } from "../../app/context-runtime";
4
+ import type { RetrievalTraceSession } from "../../core/retrieval-trace-session";
4
5
  import type { EmbeddingPort, RerankPort } from "../../llm/types";
5
6
  import type { VectorIndexPort } from "../../store/vector";
6
7
 
@@ -10,6 +11,10 @@ import {
10
11
  canonicalBuiltContextCapsuleJson,
11
12
  validateContextCapsuleBuildInput,
12
13
  } from "../../app/context-runtime";
14
+ import {
15
+ finishRetrievalTraceAfterError,
16
+ startRetrievalTraceRequest,
17
+ } from "../../core/retrieval-trace-request";
13
18
  import { LlmAdapter } from "../../llm/nodeLlamaCpp/adapter";
14
19
  import { resolveDownloadPolicy } from "../../llm/policy";
15
20
  import { resolveModelUri } from "../../llm/registry";
@@ -77,13 +82,56 @@ export const contextBuild = async (
77
82
  let embedPort: EmbeddingPort | null = null;
78
83
  let rerankPort: RerankPort | null = null;
79
84
  let vectorIndex: VectorIndexPort | null = null;
85
+ let traceSession: RetrievalTraceSession | undefined;
80
86
  try {
81
87
  validateContextCapsuleBuildInput(
82
88
  { goal, ...options },
83
89
  options.indexName,
84
90
  config.collections.map((collection) => collection.name)
85
91
  );
92
+ const collection =
93
+ options.collections?.length === 1 ? options.collections[0] : undefined;
94
+ const embedUri =
95
+ options.depthPolicy === "fast"
96
+ ? undefined
97
+ : resolveModelUri(config, "embed", undefined, collection);
98
+ const rerankUri =
99
+ options.depthPolicy === "fast"
100
+ ? undefined
101
+ : resolveModelUri(config, "rerank", undefined, collection);
102
+ const traceStart = await startRetrievalTraceRequest({
103
+ store,
104
+ config,
105
+ query: options.query ?? goal,
106
+ goal,
107
+ filters: {
108
+ limit: options.limit,
109
+ collection,
110
+ collections: [...(options.collections ?? [])].sort(),
111
+ lang: options.lang,
112
+ tagsAll: options.tagsAll,
113
+ tagsAny: options.tagsAny,
114
+ since: options.since,
115
+ until: options.until,
116
+ categories: options.categories,
117
+ author: options.author,
118
+ graph: options.graph,
119
+ candidateLimit: options.candidateLimit,
120
+ queryModes: options.queryModes,
121
+ uriPrefix: options.uriPrefix ?? undefined,
122
+ },
123
+ pipeline: "context",
124
+ indexName: options.indexName,
125
+ modelUris: [embedUri, rerankUri].filter((value): value is string =>
126
+ Boolean(value)
127
+ ),
128
+ });
129
+ if (!traceStart.ok) throw new Error(traceStart.error.message);
130
+ traceSession = traceStart.value ?? undefined;
86
131
  if (options.depthPolicy !== "fast") {
132
+ if (!(embedUri && rerankUri)) {
133
+ throw new Error("Context model identities could not be resolved");
134
+ }
87
135
  const globals = getGlobals();
88
136
  const policy = resolveDownloadPolicy(process.env, {
89
137
  offline: globals.offline,
@@ -92,9 +140,6 @@ export const contextBuild = async (
92
140
  const progress = showProgress
93
141
  ? createThrottledProgressRenderer(createProgressRenderer())
94
142
  : undefined;
95
- const collection =
96
- options.collections?.length === 1 ? options.collections[0] : undefined;
97
- const embedUri = resolveModelUri(config, "embed", undefined, collection);
98
143
  const embedResult = await llm.createEmbeddingPort(embedUri, {
99
144
  policy,
100
145
  onProgress: progress ? (value) => progress("embed", value) : undefined,
@@ -110,12 +155,6 @@ export const contextBuild = async (
110
155
  if (vectorResult.ok) vectorIndex = vectorResult.value;
111
156
  }
112
157
  }
113
- const rerankUri = resolveModelUri(
114
- config,
115
- "rerank",
116
- undefined,
117
- collection
118
- );
119
158
  const rerankResult = await llm.createRerankPort(rerankUri, {
120
159
  policy,
121
160
  onProgress: progress ? (value) => progress("rerank", value) : undefined,
@@ -132,12 +171,20 @@ export const contextBuild = async (
132
171
  vectorIndex,
133
172
  embedPort,
134
173
  rerankPort,
174
+ traceSession,
135
175
  }
136
176
  );
177
+ const finished = await traceSession?.finish("completed");
178
+ if (finished && !finished.ok) throw new Error(finished.error.message);
179
+ const traceMetadata = traceSession?.metadata();
180
+ if (traceMetadata) {
181
+ process.stderr.write(`Trace: ${traceMetadata.traceId}\n`);
182
+ }
137
183
  return options.format === "md"
138
184
  ? formatContextCapsuleMarkdown(capsule)
139
185
  : canonicalBuiltContextCapsuleJson(capsule);
140
186
  } catch (error) {
187
+ await finishRetrievalTraceAfterError(traceSession, error);
141
188
  if (error instanceof CliError) throw error;
142
189
  throw contextCliError(error);
143
190
  } finally {
@@ -1,9 +1,18 @@
1
1
  import type { CollectionSyncResult } from "../../ingestion";
2
+ import type { HttpGatewayOverrides } from "../../mcp/http-security";
2
3
  import type { BackgroundRuntimeResult } from "../../serve/background-runtime";
4
+ import type { ResidentRuntime } from "../../serve/resident-runtime";
3
5
 
6
+ import {
7
+ DEFAULT_HTTP_GATEWAY_PORT,
8
+ isHttpGatewayLoopbackBind,
9
+ resolveHttpGatewayConfig,
10
+ } from "../../mcp/http-security";
4
11
  import { startBackgroundRuntime } from "../../serve/background-runtime";
12
+ import { handleResidentStatus, handleStatus } from "../../serve/routes/api";
13
+ import { createMcpHttpGateway } from "../../serve/routes/mcp";
5
14
 
6
- export interface DaemonOptions {
15
+ export interface DaemonOptions extends HttpGatewayOverrides {
7
16
  configPath?: string;
8
17
  index?: string;
9
18
  offline?: boolean;
@@ -24,6 +33,8 @@ type DaemonLogger = {
24
33
 
25
34
  type DaemonDeps = {
26
35
  startBackgroundRuntime?: typeof startBackgroundRuntime;
36
+ createMcpHttpGateway?: typeof createMcpHttpGateway;
37
+ serve?: typeof Bun.serve;
27
38
  logger?: DaemonLogger;
28
39
  };
29
40
 
@@ -31,6 +42,18 @@ function formatCollectionSyncSummary(result: CollectionSyncResult): string {
31
42
  return `${result.collection}: ${result.filesAdded} added, ${result.filesUpdated} updated, ${result.filesUnchanged} unchanged, ${result.filesErrored} errors`;
32
43
  }
33
44
 
45
+ export function handleDaemonAppStatus(
46
+ runtime: ResidentRuntime,
47
+ host: string
48
+ ): Promise<Response> | Response {
49
+ if (!isHttpGatewayLoopbackBind(host)) {
50
+ return new Response(null, { status: 404 });
51
+ }
52
+ return handleStatus(runtime.ctxHolder.current, {
53
+ getResidentStatus: () => runtime.getStatus(),
54
+ });
55
+ }
56
+
34
57
  function createSignalPromise(
35
58
  signal: AbortSignal | undefined,
36
59
  logger: DaemonLogger,
@@ -79,6 +102,7 @@ export async function daemon(
79
102
  const runtimeResult: BackgroundRuntimeResult = await (
80
103
  deps.startBackgroundRuntime ?? startBackgroundRuntime
81
104
  )({
105
+ mode: "daemon",
82
106
  configPath: options.configPath,
83
107
  index: options.index,
84
108
  requireCollections: true,
@@ -108,11 +132,52 @@ export async function daemon(
108
132
  }
109
133
 
110
134
  const { runtime } = runtimeResult;
135
+ const gatewayConfig = resolveHttpGatewayConfig(runtime.config.gateway, {
136
+ host: options.host,
137
+ port: options.port ?? DEFAULT_HTTP_GATEWAY_PORT,
138
+ tokenFile: options.tokenFile,
139
+ allowedHosts: options.allowedHosts,
140
+ allowedOrigins: options.allowedOrigins,
141
+ enableWrite: options.enableWrite,
142
+ });
143
+ let gateway: Awaited<ReturnType<typeof createMcpHttpGateway>> | undefined;
144
+ let server: ReturnType<typeof Bun.serve> | undefined;
111
145
  try {
146
+ gateway = await (deps.createMcpHttpGateway ?? createMcpHttpGateway)(
147
+ runtime as ResidentRuntime,
148
+ gatewayConfig
149
+ );
150
+ server = (deps.serve ?? Bun.serve)({
151
+ port: gatewayConfig.port,
152
+ hostname: gatewayConfig.host,
153
+ development: false,
154
+ routes: {
155
+ "/mcp": gateway.route,
156
+ "/api/status": {
157
+ GET: () =>
158
+ handleDaemonAppStatus(
159
+ runtime as ResidentRuntime,
160
+ gatewayConfig.host
161
+ ),
162
+ },
163
+ "/api/resident/status": {
164
+ GET: () =>
165
+ handleResidentStatus(() =>
166
+ (runtime as ResidentRuntime).getStatus()
167
+ ),
168
+ },
169
+ },
170
+ });
171
+ (runtime as Partial<ResidentRuntime>).setListenerPort?.(
172
+ server.port ?? gatewayConfig.port
173
+ );
112
174
  if (!options.quiet) {
113
175
  logger.log(
114
176
  `GNO daemon started for index "${options.index ?? "default"}" using ${runtime.config.collections.length} collection${runtime.config.collections.length === 1 ? "" : "s"}.`
115
177
  );
178
+ logger.log(
179
+ `MCP gateway listening at http://${gatewayConfig.host}:${server.port}/mcp`
180
+ );
116
181
  const watchState = runtime.watchService.getState();
117
182
  if (watchState.activeCollections.length > 0) {
118
183
  logger.log(`watching: ${watchState.activeCollections.join(", ")}`);
@@ -154,6 +219,8 @@ export async function daemon(
154
219
  error: error instanceof Error ? error.message : String(error),
155
220
  };
156
221
  } finally {
157
- await runtime.dispose();
222
+ await Promise.allSettled([server?.stop(true)]);
223
+ await Promise.allSettled([gateway?.close()]);
224
+ await Promise.allSettled([runtime.dispose()]);
158
225
  }
159
226
  }
@@ -5,6 +5,10 @@
5
5
  * @module src/cli/commands/get
6
6
  */
7
7
 
8
+ import type {
9
+ RetrievalTraceSession,
10
+ RetrievalTraceSurfaceMetadata,
11
+ } from "../../core/retrieval-trace-session";
8
12
  import type { DocumentRow, StorePort, StoreResult } from "../../store/types";
9
13
  import type { ParsedRef } from "./ref-parser";
10
14
 
@@ -17,6 +21,10 @@ import {
17
21
  getDocumentCapabilities,
18
22
  type DocumentCapabilities,
19
23
  } from "../../core/document-capabilities";
24
+ import {
25
+ evidenceFromExactDocument,
26
+ RetrievalTraceSession as TraceSession,
27
+ } from "../../core/retrieval-trace-session";
20
28
  import { parseRef } from "./ref-parser";
21
29
  import { initStore } from "./shared";
22
30
 
@@ -41,10 +49,18 @@ export interface GetCommandOptions {
41
49
  json?: boolean;
42
50
  /** Markdown output */
43
51
  md?: boolean;
52
+ /** Continue an open retrieval receipt returned by a prior query. */
53
+ traceId?: string;
54
+ /** Internal shared-session seam for in-process callers. */
55
+ traceSession?: RetrievalTraceSession;
44
56
  }
45
57
 
46
58
  export type GetResult =
47
- | { success: true; data: GetResponse }
59
+ | {
60
+ success: true;
61
+ data: GetResponse;
62
+ metadata?: RetrievalTraceSurfaceMetadata;
63
+ }
48
64
  | { success: false; error: string; isValidation?: boolean };
49
65
 
50
66
  export interface GetResponse {
@@ -138,7 +154,53 @@ export async function get(
138
154
  const { store, config } = initResult;
139
155
 
140
156
  try {
141
- return await fetchDocument(store, parsed, options, config, indexName);
157
+ let traceSession = options.traceSession;
158
+ if (!traceSession && options.traceId) {
159
+ const resumed = await TraceSession.resume({
160
+ store,
161
+ config: config.retrievalTraces,
162
+ traceId: options.traceId,
163
+ });
164
+ if (!resumed.ok) {
165
+ return { success: false, error: resumed.error.message };
166
+ }
167
+ traceSession = resumed.value ?? undefined;
168
+ }
169
+ const result = await fetchDocument(
170
+ store,
171
+ parsed,
172
+ options,
173
+ config,
174
+ indexName
175
+ );
176
+ if (!result.success || !traceSession) return result;
177
+ const lines = result.data.returnedLines ?? {
178
+ start: 1,
179
+ end: result.data.totalLines,
180
+ };
181
+ const evidence = evidenceFromExactDocument({
182
+ docid: result.data.docid,
183
+ uri: result.data.uri,
184
+ sourceHash: result.data.source.sourceHash,
185
+ mirrorHash: result.data.conversion?.mirrorHash,
186
+ content: result.data.content,
187
+ startLine: lines.start,
188
+ endLine: lines.end,
189
+ });
190
+ if (evidence) {
191
+ const getEvent = await traceSession.recordEvidence("get", [evidence]);
192
+ if (!getEvent.ok) {
193
+ return { success: false, error: getEvent.error.message };
194
+ }
195
+ const openEvent = await traceSession.recordEvidence("open", [evidence]);
196
+ if (!openEvent.ok) {
197
+ return { success: false, error: openEvent.error.message };
198
+ }
199
+ }
200
+ return {
201
+ ...result,
202
+ metadata: traceSession.metadata(),
203
+ };
142
204
  } finally {
143
205
  await store.close();
144
206
  }
@@ -375,7 +437,6 @@ export function formatGet(
375
437
  }
376
438
  return `Error: ${result.error}`;
377
439
  }
378
-
379
440
  const { data } = result;
380
441
 
381
442
  if (options.json) {