@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
package/src/sdk/types.ts CHANGED
@@ -19,6 +19,17 @@ import type { ContextEvidenceErrorCode } from "../core/context-evidence";
19
19
  import type { ContextVerifierErrorCode } from "../core/context-verifier";
20
20
  import type { NoteCollisionPolicy } from "../core/note-creation";
21
21
  import type { NotePresetId } from "../core/note-presets";
22
+ import type {
23
+ RetrievalTraceDeleteResult,
24
+ RetrievalTraceDetail,
25
+ RetrievalTraceExportRequest,
26
+ RetrievalTraceExportResult,
27
+ RetrievalTraceLabelRequest,
28
+ RetrievalTraceLabelResult,
29
+ RetrievalTraceListRequest,
30
+ RetrievalTraceListResult,
31
+ RetrievalTracePurgeResult as RetrievalTraceManagementPurgeResult,
32
+ } from "../core/retrieval-trace-management";
22
33
  import type { DocumentSection } from "../core/sections";
23
34
  import type { SyncResult } from "../ingestion";
24
35
  import type { DownloadPolicy } from "../llm/policy";
@@ -88,6 +99,8 @@ export type GnoContextErrorCode =
88
99
  export interface GnoGetOptions {
89
100
  from?: number;
90
101
  limit?: number;
102
+ /** Continue an open retrieval trace returned by search/query. */
103
+ traceId?: string;
91
104
  }
92
105
 
93
106
  export interface GnoMultiGetOptions {
@@ -221,6 +234,21 @@ export interface GnoClient {
221
234
  options?: GnoListOptions
222
235
  ): Promise<import("../cli/commands/ls").LsResponse>;
223
236
  status(): Promise<IndexStatus>;
237
+ listRetrievalTraces(
238
+ options?: RetrievalTraceListRequest
239
+ ): Promise<RetrievalTraceListResult>;
240
+ getRetrievalTrace(
241
+ traceId: string,
242
+ options?: { detailLimit?: number }
243
+ ): Promise<RetrievalTraceDetail>;
244
+ labelRetrievalTrace(
245
+ input: RetrievalTraceLabelRequest
246
+ ): Promise<RetrievalTraceLabelResult>;
247
+ exportRetrievalTraces(
248
+ input: RetrievalTraceExportRequest
249
+ ): Promise<RetrievalTraceExportResult>;
250
+ deleteRetrievalTrace(traceId: string): Promise<RetrievalTraceDeleteResult>;
251
+ purgeRetrievalTraces(): Promise<RetrievalTraceManagementPurgeResult>;
224
252
  update(options?: GnoUpdateOptions): Promise<SyncResult>;
225
253
  embed(options?: GnoEmbedOptions): Promise<GnoEmbedResult>;
226
254
  index(options?: GnoIndexOptions): Promise<GnoIndexResult>;
@@ -1,46 +1,20 @@
1
- // node:path resolve has no Bun equivalent for canonical process-relative paths.
2
- import { resolve } from "node:path";
1
+ /** Backwards-compatible entrypoint for the shared resident runtime. */
3
2
 
4
3
  import type { Config } from "../config/types";
5
4
  import type { SyncResult } from "../ingestion";
6
- import type { DocumentEventBus } from "./doc-events";
5
+ import type { SqliteAdapter } from "../store/sqlite/adapter";
7
6
  import type { EmbedResult, EmbedScheduler } from "./embed-scheduler";
8
7
  import type { ContextHolder } from "./routes/api";
9
- import type {
10
- CollectionWatchCallbacks,
11
- CollectionWatchService,
12
- } from "./watch-service";
8
+ import type { CollectionWatchService } from "./watch-service";
13
9
 
14
- import { getIndexDbPath } from "../app/constants";
15
- import { INDEX_NAME_REQUIREMENTS, isValidIndexName } from "../app/index-name";
16
10
  import {
17
- ensureDirectories,
18
- formatConfigWarnings,
19
- getConfigPaths,
20
- isInitialized,
21
- loadConfig,
22
- } from "../config";
23
- import { defaultSyncService } from "../ingestion";
24
- import { withContentTypeRules } from "../ingestion";
25
- import { getActivePreset } from "../llm/registry";
26
- import { SqliteAdapter } from "../store/sqlite/adapter";
27
- import {
28
- createServerContext,
29
- type CreateServerContextOptions,
30
- disposeServerContext,
31
- type ServerContext,
32
- } from "./context";
33
- import { createEmbedScheduler } from "./embed-scheduler";
34
- import { CollectionWatchService as DefaultCollectionWatchService } from "./watch-service";
11
+ startResidentRuntime,
12
+ type ResidentRuntimeDeps,
13
+ type ResidentRuntimeOptions,
14
+ } from "./resident-runtime";
35
15
 
36
- export interface BackgroundRuntimeOptions {
37
- configPath?: string;
38
- index?: string;
39
- requireCollections?: boolean;
40
- offline?: boolean;
41
- eventBus?: DocumentEventBus | null;
42
- watchCallbacks?: CollectionWatchCallbacks;
43
- }
16
+ export type BackgroundRuntimeOptions = ResidentRuntimeOptions;
17
+ export type BackgroundRuntimeDeps = ResidentRuntimeDeps;
44
18
 
45
19
  export interface BackgroundRuntime {
46
20
  store: SqliteAdapter;
@@ -48,16 +22,13 @@ export interface BackgroundRuntime {
48
22
  actualConfigPath: string;
49
23
  ctxHolder: ContextHolder;
50
24
  scheduler: EmbedScheduler;
51
- eventBus: DocumentEventBus | null;
25
+ eventBus: import("./doc-events").DocumentEventBus | null;
52
26
  watchService: CollectionWatchService;
53
27
  syncAll(options?: {
54
28
  gitPull?: boolean;
55
29
  runUpdateCmd?: boolean;
56
30
  triggerEmbed?: boolean;
57
- }): Promise<{
58
- syncResult: SyncResult;
59
- embedResult: EmbedResult | null;
60
- }>;
31
+ }): Promise<{ syncResult: SyncResult; embedResult: EmbedResult | null }>;
61
32
  dispose(): Promise<void>;
62
33
  }
63
34
 
@@ -65,180 +36,9 @@ export type BackgroundRuntimeResult =
65
36
  | { success: true; runtime: BackgroundRuntime }
66
37
  | { success: false; error: string };
67
38
 
68
- type BackgroundRuntimeDeps = {
69
- isInitialized?: typeof isInitialized;
70
- loadConfig?: typeof loadConfig;
71
- getConfigPaths?: typeof getConfigPaths;
72
- ensureDirectories?: typeof ensureDirectories;
73
- storeFactory?: () => SqliteAdapter;
74
- createServerContext?: (
75
- store: SqliteAdapter,
76
- config: Config,
77
- options?: CreateServerContextOptions
78
- ) => Promise<ServerContext>;
79
- disposeServerContext?: (ctx: ServerContext) => Promise<void>;
80
- createEmbedScheduler?: typeof createEmbedScheduler;
81
- syncAllService?: typeof defaultSyncService.syncAll;
82
- watchServiceFactory?: (options: {
83
- collections: Config["collections"];
84
- store: SqliteAdapter;
85
- scheduler: EmbedScheduler | null;
86
- eventBus?: DocumentEventBus | null;
87
- callbacks?: CollectionWatchCallbacks;
88
- syncOptions?: Parameters<typeof withContentTypeRules>[0];
89
- }) => CollectionWatchService;
90
- };
91
-
92
39
  export async function startBackgroundRuntime(
93
40
  options: BackgroundRuntimeOptions = {},
94
41
  deps: BackgroundRuntimeDeps = {}
95
42
  ): Promise<BackgroundRuntimeResult> {
96
- if (options.index !== undefined && !isValidIndexName(options.index)) {
97
- return {
98
- success: false,
99
- error: `Invalid index name: ${INDEX_NAME_REQUIREMENTS}.`,
100
- };
101
- }
102
- const syncAllService = deps.syncAllService
103
- ? (...args: Parameters<typeof defaultSyncService.syncAll>) =>
104
- deps.syncAllService!(...args)
105
- : defaultSyncService.syncAll.bind(defaultSyncService);
106
- const initialized = await (deps.isInitialized ?? isInitialized)(
107
- options.configPath
108
- );
109
- if (!initialized) {
110
- return { success: false, error: "GNO not initialized. Run: gno init" };
111
- }
112
-
113
- const configResult = await (deps.loadConfig ?? loadConfig)(
114
- options.configPath
115
- );
116
- if (!configResult.ok) {
117
- return { success: false, error: configResult.error.message };
118
- }
119
- for (const warning of formatConfigWarnings(configResult.warnings)) {
120
- console.warn(warning);
121
- }
122
- const config = configResult.value;
123
-
124
- if (options.requireCollections && config.collections.length === 0) {
125
- return {
126
- success: false,
127
- error: "No collections configured. Run: gno collection add <path>",
128
- };
129
- }
130
-
131
- await (deps.ensureDirectories ?? ensureDirectories)();
132
-
133
- const store = deps.storeFactory ? deps.storeFactory() : new SqliteAdapter();
134
- const dbPath = getIndexDbPath(options.index);
135
- const paths = (deps.getConfigPaths ?? getConfigPaths)();
136
- const actualConfigPath = resolve(options.configPath ?? paths.configFile);
137
- store.setConfigPath(actualConfigPath);
138
-
139
- const openResult = await store.open(dbPath, config.ftsTokenizer);
140
- if (!openResult.ok) {
141
- return { success: false, error: openResult.error.message };
142
- }
143
-
144
- const syncCollResult = await store.syncCollections(config.collections);
145
- if (!syncCollResult.ok) {
146
- await store.close();
147
- return { success: false, error: syncCollResult.error.message };
148
- }
149
- const syncCtxResult = await store.syncContexts(config.contexts ?? []);
150
- if (!syncCtxResult.ok) {
151
- await store.close();
152
- return { success: false, error: syncCtxResult.error.message };
153
- }
154
-
155
- const ctx = await (deps.createServerContext ?? createServerContext)(
156
- store,
157
- config,
158
- {
159
- offline: options.offline ?? false,
160
- indexName: options.index,
161
- }
162
- );
163
-
164
- const ctxHolder: ContextHolder = {
165
- current: ctx,
166
- config,
167
- scheduler: null,
168
- eventBus: options.eventBus ?? null,
169
- watchService: null,
170
- };
171
-
172
- const scheduler = (deps.createEmbedScheduler ?? createEmbedScheduler)({
173
- db: store.getRawDb(),
174
- getEmbedPort: () => ctxHolder.current.embedPort,
175
- getVectorIndex: () => ctxHolder.current.vectorIndex,
176
- getModelUri: () => getActivePreset(ctxHolder.config).embed,
177
- });
178
- ctxHolder.scheduler = scheduler;
179
- ctxHolder.current.scheduler = scheduler;
180
- ctxHolder.current.eventBus = options.eventBus ?? null;
181
-
182
- const watchService = (
183
- deps.watchServiceFactory ??
184
- ((watchOptions) => new DefaultCollectionWatchService(watchOptions))
185
- )({
186
- collections: config.collections,
187
- store,
188
- scheduler,
189
- eventBus: options.eventBus ?? null,
190
- callbacks: options.watchCallbacks,
191
- syncOptions: withContentTypeRules({}, config),
192
- });
193
- watchService.start();
194
- ctxHolder.watchService = watchService;
195
- ctxHolder.current.watchService = watchService;
196
-
197
- let disposed = false;
198
-
199
- return {
200
- success: true,
201
- runtime: {
202
- store,
203
- config,
204
- actualConfigPath,
205
- ctxHolder,
206
- scheduler,
207
- eventBus: options.eventBus ?? null,
208
- watchService,
209
- async syncAll(syncOptions = {}) {
210
- const syncResult = await syncAllService(
211
- config.collections,
212
- store,
213
- withContentTypeRules(
214
- {
215
- gitPull: syncOptions.gitPull,
216
- runUpdateCmd: syncOptions.runUpdateCmd,
217
- },
218
- config
219
- )
220
- );
221
-
222
- let embedResult: EmbedResult | null = null;
223
- if (syncOptions.triggerEmbed !== false) {
224
- embedResult = await scheduler.triggerNow();
225
- }
226
-
227
- return { syncResult, embedResult };
228
- },
229
- async dispose() {
230
- if (disposed) {
231
- return;
232
- }
233
- disposed = true;
234
- await Promise.resolve(watchService.dispose());
235
- options.eventBus?.close();
236
- scheduler.dispose();
237
- await (deps.disposeServerContext ?? disposeServerContext)(
238
- ctxHolder.current
239
- );
240
- await store.close();
241
- },
242
- },
243
- };
43
+ return startResidentRuntime(options, deps);
244
44
  }
@@ -1,5 +1,6 @@
1
1
  /** REST adapters for the shared Context Capsule application boundary. */
2
2
 
3
+ import type { RetrievalTraceSession } from "../core/retrieval-trace-session";
3
4
  import type { GnoContextErrorCode } from "../sdk/types";
4
5
  import type { ServerContext } from "./context";
5
6
 
@@ -20,6 +21,8 @@ import {
20
21
  parseContextVerifySurfaceInput,
21
22
  } from "../app/context-surface";
22
23
  import { ContextCapsuleContractError } from "../core/context-capsule";
24
+ import { startRetrievalTraceRequest } from "../core/retrieval-trace-request";
25
+ import { withRetrievalTraceHeader } from "./retrieval-trace";
23
26
 
24
27
  const JSON_HEADERS = { "content-type": "application/json; charset=utf-8" };
25
28
  const MARKDOWN_HEADERS = {
@@ -80,6 +83,7 @@ export const handleContextBuild = async (
80
83
  context: ServerContext,
81
84
  request: Request
82
85
  ): Promise<Response> => {
86
+ let traceSession: RetrievalTraceSession | null = null;
83
87
  try {
84
88
  const { input, format } = parseContextBuildSurfaceInput(
85
89
  await parseJsonBody(request),
@@ -90,6 +94,42 @@ export const handleContextBuild = async (
90
94
  context.indexName,
91
95
  context.config.collections.map((collection) => collection.name)
92
96
  );
97
+ const started = await startRetrievalTraceRequest({
98
+ store: context.store,
99
+ config: context.config,
100
+ query: input.query ?? input.goal,
101
+ goal: input.goal,
102
+ filters: {
103
+ limit: input.limit,
104
+ collection:
105
+ input.collections?.length === 1 ? input.collections[0] : undefined,
106
+ collections: [...(input.collections ?? [])].sort(),
107
+ lang: input.lang,
108
+ tagsAll: input.tagsAll,
109
+ tagsAny: input.tagsAny,
110
+ since: input.since,
111
+ until: input.until,
112
+ categories: input.categories,
113
+ author: input.author,
114
+ graph: input.graph,
115
+ candidateLimit: input.candidateLimit,
116
+ queryModes: input.queryModes,
117
+ uriPrefix: input.uriPrefix ?? undefined,
118
+ },
119
+ pipeline: "context",
120
+ indexName: context.indexName,
121
+ modelUris: [
122
+ context.embedPort?.modelUri,
123
+ context.rerankPort?.modelUri,
124
+ ].filter((value): value is string => Boolean(value)),
125
+ });
126
+ if (!started.ok) {
127
+ throw Object.assign(
128
+ new Error(`Retrieval trace start failed: ${started.error.message}`),
129
+ { code: "retrieval_failed" }
130
+ );
131
+ }
132
+ traceSession = started.value;
93
133
  const capsule = await buildContextCapsule(input, {
94
134
  store: context.store,
95
135
  config: context.config,
@@ -97,16 +137,35 @@ export const handleContextBuild = async (
97
137
  vectorIndex: context.vectorIndex,
98
138
  embedPort: context.embedPort,
99
139
  rerankPort: context.rerankPort,
140
+ traceSession: traceSession ?? undefined,
100
141
  });
101
- return format === "md"
102
- ? new Response(formatContextCapsuleMarkdown(capsule), {
103
- headers: MARKDOWN_HEADERS,
104
- })
105
- : new Response(canonicalBuiltContextCapsuleJson(capsule), {
106
- headers: JSON_HEADERS,
107
- });
142
+ const finished = await traceSession?.finish(
143
+ request.signal.aborted ? "cancelled" : "completed"
144
+ );
145
+ if (finished && !finished.ok) {
146
+ throw Object.assign(
147
+ new Error(
148
+ `Retrieval trace finalization failed: ${finished.error.message}`
149
+ ),
150
+ { code: "retrieval_failed" }
151
+ );
152
+ }
153
+ const response =
154
+ format === "md"
155
+ ? new Response(formatContextCapsuleMarkdown(capsule), {
156
+ headers: MARKDOWN_HEADERS,
157
+ })
158
+ : new Response(canonicalBuiltContextCapsuleJson(capsule), {
159
+ headers: JSON_HEADERS,
160
+ });
161
+ return withRetrievalTraceHeader(response, traceSession);
108
162
  } catch (error) {
109
- return errorResponse(error);
163
+ if (traceSession) {
164
+ await traceSession.finish(
165
+ request.signal.aborted ? "cancelled" : "failed"
166
+ );
167
+ }
168
+ return withRetrievalTraceHeader(errorResponse(error), traceSession);
110
169
  }
111
170
  };
112
171
 
@@ -7,6 +7,7 @@
7
7
 
8
8
  import type { Database } from "bun:sqlite";
9
9
 
10
+ import type { ModelLease } from "../llm/nodeLlamaCpp/lifecycle";
10
11
  import type { EmbeddingPort } from "../llm/types";
11
12
  import type { VectorIndexPort } from "../store/vector";
12
13
 
@@ -46,6 +47,9 @@ export interface EmbedSchedulerDeps {
46
47
  getVectorIndex: () => VectorIndexPort | null;
47
48
  /** Getter for current model URI (survives preset changes) */
48
49
  getModelUri: () => string;
50
+ acquireModelLease?: () => ModelLease;
51
+ onEmbedded?: (result: EmbedResult) => void;
52
+ embedBacklogFn?: typeof embedBacklog;
49
53
  }
50
54
 
51
55
  // ─────────────────────────────────────────────────────────────────────────────
@@ -63,7 +67,7 @@ export interface EmbedScheduler {
63
67
  getState(): EmbedSchedulerState;
64
68
 
65
69
  /** Cleanup on server shutdown */
66
- dispose(): void;
70
+ dispose(): Promise<void>;
67
71
  }
68
72
 
69
73
  /**
@@ -71,7 +75,15 @@ export interface EmbedScheduler {
71
75
  * Uses getters to resolve dependencies at execution time (survives context reloads).
72
76
  */
73
77
  export function createEmbedScheduler(deps: EmbedSchedulerDeps): EmbedScheduler {
74
- const { db, getEmbedPort, getVectorIndex, getModelUri } = deps;
78
+ const {
79
+ db,
80
+ getEmbedPort,
81
+ getVectorIndex,
82
+ getModelUri,
83
+ acquireModelLease,
84
+ onEmbedded,
85
+ embedBacklogFn = embedBacklog,
86
+ } = deps;
75
87
 
76
88
  // State
77
89
  let pendingCount = 0; // Track pending triggers (not actual docIds - we embed full backlog)
@@ -81,6 +93,7 @@ export function createEmbedScheduler(deps: EmbedSchedulerDeps): EmbedScheduler {
81
93
  let firstPendingAt: number | null = null;
82
94
  let nextRunAt: number | null = null; // Accurate timer due time
83
95
  let disposed = false;
96
+ let currentRun: Promise<EmbedResult | null> | null = null;
84
97
  let lastRunAt: number | null = null;
85
98
  let lastResult: EmbedResult | null = null;
86
99
 
@@ -103,20 +116,25 @@ export function createEmbedScheduler(deps: EmbedSchedulerDeps): EmbedScheduler {
103
116
  return { embedded: 0, errors: 0 };
104
117
  }
105
118
 
106
- const result = await embedBacklog({
107
- statsPort: stats,
108
- embedPort,
109
- vectorIndex,
110
- modelUri,
111
- batchSize: BATCH_SIZE,
112
- });
113
-
114
- if (!result.ok) {
115
- console.error("[embed-scheduler] Embed failed:", result.error.message);
116
- return { embedded: 0, errors: 0 };
119
+ const lease = acquireModelLease?.();
120
+ try {
121
+ const result = await embedBacklogFn({
122
+ statsPort: stats,
123
+ embedPort,
124
+ vectorIndex,
125
+ modelUri,
126
+ batchSize: BATCH_SIZE,
127
+ });
128
+
129
+ if (!result.ok) {
130
+ console.error("[embed-scheduler] Embed failed:", result.error.message);
131
+ return { embedded: 0, errors: 0 };
132
+ }
133
+ if (result.value.embedded > 0) onEmbedded?.(result.value);
134
+ return result.value;
135
+ } finally {
136
+ lease?.release();
117
137
  }
118
-
119
- return result.value;
120
138
  }
121
139
 
122
140
  /**
@@ -166,41 +184,53 @@ export function createEmbedScheduler(deps: EmbedSchedulerDeps): EmbedScheduler {
166
184
  /**
167
185
  * Execute the embed run with concurrency guard.
168
186
  */
169
- async function executeRun(): Promise<EmbedResult | null> {
187
+ function executeRun(): Promise<EmbedResult | null> {
170
188
  if (disposed || running) {
171
189
  needsRerun = true;
172
- return null;
190
+ return Promise.resolve(null);
173
191
  }
174
192
 
175
- running = true;
176
- timer = null;
177
- nextRunAt = null;
178
-
179
- // Clear pending state at START so new notifications accumulate
180
- pendingCount = 0;
181
- firstPendingAt = null;
193
+ const operation = (async (): Promise<EmbedResult | null> => {
194
+ running = true;
195
+ timer = null;
196
+ nextRunAt = null;
182
197
 
183
- let result: EmbedResult;
184
- try {
185
- result = await runEmbed();
186
- lastRunAt = Date.now();
187
- lastResult = result;
188
- } finally {
189
- running = false;
190
- }
198
+ // Clear pending state at START so new notifications accumulate
199
+ pendingCount = 0;
200
+ firstPendingAt = null;
201
+
202
+ let result: EmbedResult;
203
+ try {
204
+ result = await runEmbed();
205
+ lastRunAt = Date.now();
206
+ lastResult = result;
207
+ } finally {
208
+ running = false;
209
+ }
191
210
 
192
- // Check if we need to rerun (notifications arrived while running)
193
- // Must be AFTER running=false so scheduleRun() actually schedules
194
- if ((needsRerun || pendingCount > 0) && !disposed) {
195
- needsRerun = false;
196
- // Set firstPendingAt if we have pending work
197
- if (pendingCount > 0 && firstPendingAt === null) {
198
- firstPendingAt = Date.now();
211
+ // Check if we need to rerun (notifications arrived while running)
212
+ // Must be AFTER running=false so scheduleRun() actually schedules
213
+ if ((needsRerun || pendingCount > 0) && !disposed) {
214
+ needsRerun = false;
215
+ // Set firstPendingAt if we have pending work
216
+ if (pendingCount > 0 && firstPendingAt === null) {
217
+ firstPendingAt = Date.now();
218
+ }
219
+ scheduleRun();
199
220
  }
200
- scheduleRun();
201
- }
202
221
 
203
- return result;
222
+ return result;
223
+ })();
224
+ currentRun = operation;
225
+ void operation.then(
226
+ () => {
227
+ if (currentRun === operation) currentRun = null;
228
+ },
229
+ () => {
230
+ if (currentRun === operation) currentRun = null;
231
+ }
232
+ );
233
+ return operation;
204
234
  }
205
235
 
206
236
  return {
@@ -263,13 +293,14 @@ export function createEmbedScheduler(deps: EmbedSchedulerDeps): EmbedScheduler {
263
293
  return state;
264
294
  },
265
295
 
266
- dispose(): void {
296
+ async dispose(): Promise<void> {
267
297
  disposed = true;
268
298
  if (timer) {
269
299
  clearTimeout(timer);
270
300
  timer = null;
271
301
  nextRunAt = null;
272
302
  }
303
+ await currentRun;
273
304
  },
274
305
  };
275
306
  }
@@ -11,3 +11,12 @@ export {
11
11
  type BackgroundRuntimeResult,
12
12
  startBackgroundRuntime,
13
13
  } from "./background-runtime";
14
+ export {
15
+ type ResidentGeneration,
16
+ type ResidentMode,
17
+ type ResidentRequestHandle,
18
+ type ResidentRuntime,
19
+ type ResidentRuntimeOptions,
20
+ type ResidentRuntimeResult,
21
+ startResidentRuntime,
22
+ } from "./resident-runtime";