@zosmaai/pi-llm-wiki 0.10.9 → 0.11.1

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 (82) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.de.md +35 -4
  3. package/README.es.md +260 -170
  4. package/README.fr.md +35 -4
  5. package/README.hi.md +35 -4
  6. package/README.ja.md +35 -4
  7. package/README.ko.md +35 -4
  8. package/README.md +41 -12
  9. package/README.pt.md +35 -4
  10. package/README.ru.md +35 -4
  11. package/README.zh.md +260 -170
  12. package/assets/demo.gif +0 -0
  13. package/dist/extensions/llm-wiki/lib/bootstrap.js +74 -0
  14. package/dist/extensions/llm-wiki/lib/embeddings.js +401 -0
  15. package/dist/extensions/llm-wiki/lib/guardrails.js +232 -0
  16. package/dist/extensions/llm-wiki/lib/indexing.js +78 -0
  17. package/dist/extensions/llm-wiki/lib/ingest-worker.js +410 -0
  18. package/dist/extensions/llm-wiki/lib/inject.js +65 -0
  19. package/dist/extensions/llm-wiki/lib/knowledge-document.js +442 -0
  20. package/dist/extensions/llm-wiki/lib/knowledge-links.js +206 -0
  21. package/dist/extensions/llm-wiki/lib/legacy-repair.js +443 -0
  22. package/dist/extensions/llm-wiki/lib/metadata.js +505 -0
  23. package/dist/extensions/llm-wiki/lib/model-command.js +85 -0
  24. package/dist/extensions/llm-wiki/lib/observation.js +283 -0
  25. package/dist/extensions/llm-wiki/lib/recall.js +875 -0
  26. package/dist/extensions/llm-wiki/lib/retro.js +158 -0
  27. package/dist/extensions/llm-wiki/lib/runtime.js +187 -0
  28. package/dist/extensions/llm-wiki/lib/source-extractors.js +426 -0
  29. package/dist/extensions/llm-wiki/lib/source-packet.js +229 -0
  30. package/dist/extensions/llm-wiki/lib/subagent.js +41 -0
  31. package/dist/extensions/llm-wiki/lib/task-config.js +201 -0
  32. package/dist/extensions/llm-wiki/lib/tools.js +1199 -0
  33. package/dist/extensions/llm-wiki/lib/trajectories-command.js +51 -0
  34. package/dist/extensions/llm-wiki/lib/trajectory.js +467 -0
  35. package/dist/extensions/llm-wiki/lib/utils.js +353 -0
  36. package/dist/extensions/llm-wiki/lib/vault-format.js +247 -0
  37. package/dist/extensions/llm-wiki/lib/visible-status.js +31 -0
  38. package/dist/extensions/llm-wiki/lib/wiki-service.js +128 -0
  39. package/dist/mcp/exec.js +121 -0
  40. package/dist/mcp/index.js +229 -0
  41. package/dist/mcp/operations.js +130 -0
  42. package/dist/package.json +1 -0
  43. package/docs/api.md +5 -2
  44. package/docs/architecture.md +5 -2
  45. package/docs/configuration.md +25 -0
  46. package/docs/superpowers/plans/2026-08-02-okf-foundation.md +1579 -0
  47. package/docs/superpowers/plans/2026-08-03-okf-foundation-remediation.md +3005 -0
  48. package/docs/superpowers/plans/2026-08-06-authoritative-event-history-phase-1-foundation-hardening.md +937 -0
  49. package/docs/superpowers/plans/2026-08-06-okf-foundation-release-remediation.md +1174 -0
  50. package/docs/superpowers/plans/2026-08-07-synthesis-language.md +98 -0
  51. package/docs/superpowers/specs/2026-08-02-okf-foundation-design.md +593 -0
  52. package/docs/superpowers/specs/2026-08-02-okf-v0.2-interoperability-design.md +542 -0
  53. package/docs/superpowers/specs/2026-08-07-synthesis-language-design.md +94 -0
  54. package/extensions/llm-wiki/index.ts +22 -36
  55. package/extensions/llm-wiki/lib/bootstrap.ts +87 -0
  56. package/extensions/llm-wiki/lib/embeddings.ts +9 -3
  57. package/extensions/llm-wiki/lib/guardrails.ts +26 -18
  58. package/extensions/llm-wiki/lib/indexing.ts +2 -1
  59. package/extensions/llm-wiki/lib/ingest-worker.ts +304 -28
  60. package/extensions/llm-wiki/lib/knowledge-document.ts +663 -0
  61. package/extensions/llm-wiki/lib/knowledge-links.ts +282 -0
  62. package/extensions/llm-wiki/lib/legacy-repair.ts +572 -0
  63. package/extensions/llm-wiki/lib/metadata.ts +550 -128
  64. package/extensions/llm-wiki/lib/model-command.ts +0 -1
  65. package/extensions/llm-wiki/lib/observation.ts +37 -43
  66. package/extensions/llm-wiki/lib/recall.ts +61 -33
  67. package/extensions/llm-wiki/lib/retro.ts +65 -41
  68. package/extensions/llm-wiki/lib/runtime.ts +0 -3
  69. package/extensions/llm-wiki/lib/source-extractors.ts +12 -17
  70. package/extensions/llm-wiki/lib/source-packet.ts +45 -32
  71. package/extensions/llm-wiki/lib/task-config.ts +36 -0
  72. package/extensions/llm-wiki/lib/tools.ts +413 -342
  73. package/extensions/llm-wiki/lib/trajectory.ts +15 -1
  74. package/extensions/llm-wiki/lib/utils.ts +127 -131
  75. package/extensions/llm-wiki/lib/vault-format.ts +363 -0
  76. package/extensions/llm-wiki/lib/wiki-service.ts +183 -0
  77. package/mcp/exec.ts +122 -0
  78. package/mcp/index.ts +60 -250
  79. package/mcp/operations.ts +176 -0
  80. package/package.json +8 -2
  81. package/scripts/migrate-llm-wiki.js +801 -0
  82. package/skills/llm-wiki/SKILL.md +12 -8
@@ -4,9 +4,19 @@ import type { AgentTool } from "@mariozechner/pi-agent-core";
4
4
  import type { Api, Model } from "@mariozechner/pi-ai";
5
5
  import { Type } from "typebox";
6
6
  import type { Static } from "typebox";
7
+ import {
8
+ type KnowledgeDiagnostic,
9
+ type KnowledgeDocument,
10
+ createKnowledgeDocument,
11
+ patchKnowledgeDocument,
12
+ readKnowledgeDocumentFile,
13
+ serializeKnowledgeDocument,
14
+ writeKnowledgeDocumentFile,
15
+ } from "./knowledge-document.js";
7
16
  import { appendEvent, rebuildMetadataLight } from "./metadata.js";
8
17
  import { runSubAgent } from "./subagent.js";
9
18
  import { type VaultPaths, fmtDate, slugify } from "./utils.js";
19
+ import { VaultWriteError, assertWritableVault } from "./vault-format.js";
10
20
 
11
21
  /**
12
22
  * Background ingest synthesis (issue #65, part of epic #63).
@@ -76,64 +86,239 @@ export interface CommitResult {
76
86
  contradictions: number;
77
87
  }
78
88
 
89
+ export type CommitSynthesisOutcome =
90
+ | ({ ok: true } & CommitResult)
91
+ | { ok: false; sourceId: string; diagnostics: KnowledgeDiagnostic[] };
92
+
79
93
  // ── deterministic persistence (no LLM) ────────────────────
80
94
 
81
- function buildEntityPage(
95
+ /** Localized headings for generated pages (issue #124). */
96
+ const HEADINGS: Record<string, Record<string, string>> = {
97
+ ru: {
98
+ summary: "Резюме",
99
+ keyTakeaways: "Ключевые выводы",
100
+ entitiesMentioned: "Упомянутые сущности",
101
+ conceptsMentioned: "Упомянутые концепции",
102
+ notableQuotes: "Заметные цитаты",
103
+ contradictions: "Противоречия",
104
+ sourcePacket: "Пакет источника",
105
+ overview: "Обзор",
106
+ definition: "Определение",
107
+ noneRecorded: "[Не записано]",
108
+ none: "[Нет]",
109
+ id: "ID",
110
+ extracted: "Извлечено",
111
+ manifest: "Манифест",
112
+ contradiction: "⚠️ **Противоречие**",
113
+ },
114
+ fr: {
115
+ summary: "Résumé",
116
+ keyTakeaways: "Points clés",
117
+ entitiesMentioned: "Entités mentionnées",
118
+ conceptsMentioned: "Concepts mentionnés",
119
+ notableQuotes: "Citations notables",
120
+ contradictions: "Contradictions",
121
+ sourcePacket: "Paquet source",
122
+ overview: "Aperçu",
123
+ definition: "Définition",
124
+ noneRecorded: "[Aucun enregistré]",
125
+ none: "[Aucun]",
126
+ id: "ID",
127
+ extracted: "Extrait",
128
+ manifest: "Manifeste",
129
+ contradiction: "⚠️ **Contradiction**",
130
+ },
131
+ de: {
132
+ summary: "Zusammenfassung",
133
+ keyTakeaways: "Wichtige Erkenntnisse",
134
+ entitiesMentioned: "Erwähnte Entitäten",
135
+ conceptsMentioned: "Erwähnte Konzepte",
136
+ notableQuotes: "Bemerkenswerte Zitate",
137
+ contradictions: "Widersprüche",
138
+ sourcePacket: "Quellenpaket",
139
+ overview: "Übersicht",
140
+ definition: "Definition",
141
+ noneRecorded: "[Keine aufgezeichnet]",
142
+ none: "[Keine]",
143
+ id: "ID",
144
+ extracted: "Extrahiert",
145
+ manifest: "Manifest",
146
+ contradiction: "⚠️ **Widerspruch**",
147
+ },
148
+ ja: {
149
+ summary: "要約",
150
+ keyTakeaways: "主な要点",
151
+ entitiesMentioned: "言及されたエンティティ",
152
+ conceptsMentioned: "言及された概念",
153
+ notableQuotes: "注目すべき引用",
154
+ contradictions: "矛盾",
155
+ sourcePacket: "ソースパケット",
156
+ overview: "概要",
157
+ definition: "定義",
158
+ noneRecorded: "[記録なし]",
159
+ none: "[なし]",
160
+ id: "ID",
161
+ extracted: "抽出済み",
162
+ manifest: "マニフェスト",
163
+ contradiction: "⚠️ **矛盾**",
164
+ },
165
+ };
166
+
167
+ function getHeadings(lang?: string): Record<string, string> {
168
+ if (lang && HEADINGS[lang]) return HEADINGS[lang];
169
+ // English defaults
170
+ return {
171
+ summary: "Summary",
172
+ keyTakeaways: "Key Takeaways",
173
+ entitiesMentioned: "Entities Mentioned",
174
+ conceptsMentioned: "Concepts Mentioned",
175
+ notableQuotes: "Notable Quotes",
176
+ contradictions: "Contradictions",
177
+ sourcePacket: "Source Packet",
178
+ overview: "Overview",
179
+ definition: "Definition",
180
+ noneRecorded: "[None recorded]",
181
+ none: "[None]",
182
+ id: "ID",
183
+ extracted: "Extracted",
184
+ manifest: "Manifest",
185
+ contradiction: "⚠️ **Contradiction**",
186
+ };
187
+ }
188
+
189
+ function buildEntityPageBody(
82
190
  title: string,
83
191
  description: string,
84
- date: string,
85
192
  sourceId: string,
193
+ lang?: string,
86
194
  ): string {
87
195
  const desc = description.trim() || "One-line description.";
88
- return `---\ntype: entity\ncreated: ${date}\nupdated: ${date}\nsources: [[[sources/${sourceId}]]]\n---\n\n# ${title}\n\n${desc}\n\n## Overview\n\n[Key facts]\n\n## Links\n\n- [[sources/${sourceId}]]\n`;
196
+ const h = getHeadings(lang);
197
+ return `# ${title}
198
+
199
+ ${desc}
200
+
201
+ ## ${h.overview}
202
+
203
+ [Key facts]
204
+
205
+ ## Links
206
+
207
+ - [${sourceId}](/sources/${sourceId}.md)`;
89
208
  }
90
209
 
91
- function buildConceptPage(
210
+ function buildConceptPageBody(
92
211
  title: string,
93
212
  definition: string,
94
- date: string,
95
213
  sourceId: string,
214
+ lang?: string,
96
215
  ): string {
97
216
  const def = definition.trim() || "One-line definition.";
98
- return `---\ntype: concept\ncreated: ${date}\nupdated: ${date}\nsources: [[[sources/${sourceId}]]]\n---\n\n# ${title}\n\n${def}\n\n## Definition\n\n[Clear explanation]\n\n## Links\n\n- [[sources/${sourceId}]]\n`;
217
+ const h = getHeadings(lang);
218
+ return `# ${title}
219
+
220
+ ${def}
221
+
222
+ ## ${h.definition}
223
+
224
+ [Clear explanation]
225
+
226
+ ## Links
227
+
228
+ - [${sourceId}](/sources/${sourceId}.md)`;
99
229
  }
100
230
 
101
- /** Rebuild the source page from synthesis data, marking it ingested. */
102
- export function buildIngestedSourcePage(
231
+ /** Rebuild the source page body from synthesis data, marking it ingested. */
232
+ export function buildIngestedSourcePageBody(
103
233
  manifest: Record<string, unknown>,
104
234
  data: SynthesisData,
105
- date: string,
235
+ _date: string,
236
+ lang?: string,
106
237
  ): string {
107
238
  const id = String(manifest.id);
108
239
  const title = String(manifest.title || id);
109
240
  const url = manifest.url ? `\n> _Original: [${manifest.url}](${manifest.url})_` : "";
110
- const format = String(manifest.format || "unknown");
111
- const captured = String(manifest.captured || date);
241
+ const h = getHeadings(lang);
112
242
 
113
243
  const takeaways =
114
244
  data.key_takeaways.length > 0
115
245
  ? data.key_takeaways.map((t) => `- ${t.trim()}`).join("\n")
116
- : "- [None recorded]";
246
+ : `- ${h.noneRecorded}`;
117
247
  const entities =
118
248
  data.entities.length > 0
119
- ? data.entities.map((e) => `- [[entities/${slugify(e.title)}]]`).join("\n")
120
- : "- [None]";
249
+ ? data.entities.map((e) => `- [${e.title}](/entities/${slugify(e.title)}.md)`).join("\n")
250
+ : `- ${h.none}`;
121
251
  const concepts =
122
252
  data.concepts.length > 0
123
- ? data.concepts.map((c) => `- [[concepts/${slugify(c.title)}]]`).join("\n")
124
- : "- [None]";
253
+ ? data.concepts.map((c) => `- [${c.title}](/concepts/${slugify(c.title)}.md)`).join("\n")
254
+ : `- ${h.none}`;
125
255
  const quotes =
126
256
  data.quotes && data.quotes.length > 0
127
257
  ? data.quotes
128
258
  .map((q) => `> ${q.text.trim()}${q.attribution ? ` — ${q.attribution}` : ""}`)
129
259
  .join("\n\n")
130
- : "> [None recorded]";
260
+ : `> ${h.noneRecorded}`;
131
261
  const contradictions =
132
262
  data.contradictions && data.contradictions.length > 0
133
- ? `\n## Contradictions\n\n${data.contradictions.map((c) => `⚠️ **Contradiction**: ${c.trim()}`).join("\n")}\n`
263
+ ? `\n## ${h.contradictions}\n\n${data.contradictions.map((c) => `${h.contradiction}: ${c.trim()}`).join("\n")}\n`
134
264
  : "";
135
265
 
136
- return `---\ntype: source\nformat: ${format}\nsource_id: ${id}\nraw_path: raw/sources/${id}/extracted.md\ncaptured: ${captured}\nstatus: ingested\nupdated: ${date}\n---\n\n# ${title}${url}\n\n## Summary\n\n${data.summary.trim()}\n\n## Key Takeaways\n\n${takeaways}\n\n## Entities Mentioned\n\n${entities}\n\n## Concepts Mentioned\n\n${concepts}\n\n## Notable Quotes\n\n${quotes}\n${contradictions}\n## Source Packet\n\n- **ID:** \`[[sources/${id}]]\`\n- **Extracted:** [raw/sources/${id}/extracted.md](../raw/sources/${id}/extracted.md)\n- **Manifest:** [raw/sources/${id}/manifest.json](../raw/sources/${id}/manifest.json)\n`;
266
+ return `# ${title}${url}
267
+
268
+ ## ${h.summary}
269
+
270
+ ${data.summary.trim()}
271
+
272
+ ## ${h.keyTakeaways}
273
+
274
+ ${takeaways}
275
+
276
+ ## ${h.entitiesMentioned}
277
+
278
+ ${entities}
279
+
280
+ ## ${h.conceptsMentioned}
281
+
282
+ ${concepts}
283
+
284
+ ## ${h.notableQuotes}
285
+
286
+ ${quotes}
287
+ ${contradictions}## ${h.sourcePacket}
288
+
289
+ - **${h.id}:** \`sources/${id}\`
290
+ - **${h.extracted}:** \`raw/sources/${id}/extracted.md\`
291
+ - **${h.manifest}:** \`raw/sources/${id}/manifest.json\`
292
+ `;
293
+ }
294
+
295
+ /** Rebuild the source page from synthesis data, marking it ingested. */
296
+ export function buildIngestedSourcePage(
297
+ manifest: Record<string, unknown>,
298
+ data: SynthesisData,
299
+ date: string,
300
+ lang?: string,
301
+ ): string {
302
+ const id = String(manifest.id);
303
+ const title = String(manifest.title || id);
304
+ const format = String(manifest.format || "unknown");
305
+ const captured = String(manifest.captured || date);
306
+ const body = buildIngestedSourcePageBody(manifest, data, date, lang);
307
+ const doc = createKnowledgeDocument(
308
+ `sources/${id}.md`,
309
+ {
310
+ type: "source",
311
+ title,
312
+ format,
313
+ source_id: id,
314
+ raw_path: `raw/sources/${id}/extracted.md`,
315
+ captured,
316
+ status: "ingested",
317
+ updated: date,
318
+ },
319
+ body,
320
+ );
321
+ return serializeKnowledgeDocument(doc);
137
322
  }
138
323
 
139
324
  /**
@@ -147,7 +332,8 @@ export function commitSynthesis(
147
332
  manifest: Record<string, unknown>,
148
333
  data: SynthesisData,
149
334
  date: string = fmtDate(),
150
- ): CommitResult {
335
+ lang?: string,
336
+ ): CommitSynthesisOutcome {
151
337
  const result: CommitResult = {
152
338
  sourceId,
153
339
  sourcePage: join(paths.wiki, "sources", `${sourceId}.md`),
@@ -158,9 +344,42 @@ export function commitSynthesis(
158
344
  contradictions: data.contradictions?.length ?? 0,
159
345
  };
160
346
 
161
- // Source page (always rewritten from skeleton → ingested).
347
+ try {
348
+ assertWritableVault(paths);
349
+ } catch (error: unknown) {
350
+ if (error instanceof VaultWriteError) {
351
+ return { ok: false, sourceId, diagnostics: error.diagnostics };
352
+ }
353
+ throw error;
354
+ }
355
+
356
+ // Patch existing documents so unknown fields, legacy sources, and titles survive.
357
+ let sourceDocument: KnowledgeDocument;
358
+ if (existsSync(result.sourcePage)) {
359
+ const parsed = readKnowledgeDocumentFile(result.sourcePage, `sources/${sourceId}.md`);
360
+ if (!parsed.ok) return { ok: false, sourceId, diagnostics: parsed.diagnostics };
361
+ sourceDocument = patchKnowledgeDocument(parsed.document, {
362
+ fields: { status: "ingested", updated: date },
363
+ body: buildIngestedSourcePageBody(manifest, data, date, lang),
364
+ });
365
+ } else {
366
+ sourceDocument = createKnowledgeDocument(
367
+ `sources/${sourceId}.md`,
368
+ {
369
+ type: "source",
370
+ title: String(manifest.title || sourceId),
371
+ format: String(manifest.format || "unknown"),
372
+ source_id: sourceId,
373
+ raw_path: `raw/sources/${sourceId}/extracted.md`,
374
+ captured: String(manifest.captured || date),
375
+ status: "ingested",
376
+ updated: date,
377
+ },
378
+ buildIngestedSourcePageBody(manifest, data, date, lang),
379
+ );
380
+ }
162
381
  mkdirSync(join(paths.wiki, "sources"), { recursive: true });
163
- writeFileSync(result.sourcePage, buildIngestedSourcePage(manifest, data, date), "utf-8");
382
+ writeKnowledgeDocumentFile(result.sourcePage, sourceDocument);
164
383
 
165
384
  // Entity pages — create if absent, link if present.
166
385
  mkdirSync(join(paths.wiki, "entities"), { recursive: true });
@@ -171,7 +390,19 @@ export function commitSynthesis(
171
390
  if (existsSync(pagePath)) {
172
391
  result.entitiesLinked.push(slug);
173
392
  } else {
174
- writeFileSync(pagePath, buildEntityPage(e.title, e.description, date, sourceId), "utf-8");
393
+ const entityDoc = createKnowledgeDocument(
394
+ `entities/${slug}.md`,
395
+ {
396
+ type: "entity",
397
+ title: e.title,
398
+ description: e.description.trim() || "One-line description.",
399
+ created: date,
400
+ updated: date,
401
+ },
402
+ buildEntityPageBody(e.title, e.description, sourceId, lang),
403
+ [{ id: sourceId, resource: `/sources/${sourceId}.md` }],
404
+ );
405
+ writeKnowledgeDocumentFile(pagePath, entityDoc);
175
406
  result.entitiesCreated.push(slug);
176
407
  }
177
408
  }
@@ -185,7 +416,19 @@ export function commitSynthesis(
185
416
  if (existsSync(pagePath)) {
186
417
  result.conceptsLinked.push(slug);
187
418
  } else {
188
- writeFileSync(pagePath, buildConceptPage(c.title, c.definition, date, sourceId), "utf-8");
419
+ const conceptDoc = createKnowledgeDocument(
420
+ `concepts/${slug}.md`,
421
+ {
422
+ type: "concept",
423
+ title: c.title,
424
+ description: c.definition.trim() || "One-line definition.",
425
+ created: date,
426
+ updated: date,
427
+ },
428
+ buildConceptPageBody(c.title, c.definition, sourceId, lang),
429
+ [{ id: sourceId, resource: `/sources/${sourceId}.md` }],
430
+ );
431
+ writeKnowledgeDocumentFile(pagePath, conceptDoc);
189
432
  result.conceptsCreated.push(slug);
190
433
  }
191
434
  }
@@ -199,7 +442,7 @@ export function commitSynthesis(
199
442
  background: true,
200
443
  });
201
444
 
202
- return result;
445
+ return { ok: true, ...result };
203
446
  }
204
447
 
205
448
  // ── sub-agent synthesis (LLM) ─────────────────────────────
@@ -230,6 +473,8 @@ export interface RunIngestSynthesisArgs {
230
473
  /** Cap on extracted chars fed to the model (avoid huge prompts). Default 24k. */
231
474
  maxChars?: number;
232
475
  signal?: AbortSignal;
476
+ /** BCP 47 language tag for narrative content (issue #124). */
477
+ synthesisLanguage?: string;
233
478
  }
234
479
 
235
480
  /**
@@ -240,10 +485,26 @@ export interface RunIngestSynthesisArgs {
240
485
  export async function runIngestSynthesis(
241
486
  args: RunIngestSynthesisArgs,
242
487
  ): Promise<CommitResult | undefined> {
243
- const { model, apiKey, headers, paths, sourceId, manifest, extracted, maxChars, signal } = args;
488
+ const {
489
+ model,
490
+ apiKey,
491
+ headers,
492
+ paths,
493
+ sourceId,
494
+ manifest,
495
+ extracted,
496
+ maxChars,
497
+ signal,
498
+ synthesisLanguage,
499
+ } = args;
244
500
  const content = extracted.slice(0, maxChars ?? 24_000);
245
501
  if (!content.trim()) return undefined;
246
502
 
503
+ const languageInstruction = synthesisLanguage
504
+ ? `\n\nWrite all generated content in ${synthesisLanguage}, including titles, headings, summaries, descriptions, and concept/entity names. Only preserve code, API names, file paths, commands, exact technical identifiers, and verbatim quotations in their original form.`
505
+ : "";
506
+ const systemPrompt = INGEST_SYSTEM + languageInstruction;
507
+
247
508
  let committed: CommitResult | undefined;
248
509
 
249
510
  const commitTool: AgentTool<typeof CommitSynthesisSchema> = {
@@ -253,7 +514,22 @@ export async function runIngestSynthesis(
253
514
  "Persist the structured synthesis of this source into wiki pages. Call exactly once.",
254
515
  parameters: CommitSynthesisSchema,
255
516
  execute: async (_id, params) => {
256
- committed = commitSynthesis(paths, sourceId, manifest, params);
517
+ const outcome = commitSynthesis(
518
+ paths,
519
+ sourceId,
520
+ manifest,
521
+ params,
522
+ undefined,
523
+ synthesisLanguage,
524
+ );
525
+ if (!outcome.ok) {
526
+ return {
527
+ content: [{ type: "text", text: `Failed: ${outcome.diagnostics[0].message}` }],
528
+ details: { sourceId },
529
+ isError: true,
530
+ };
531
+ }
532
+ committed = outcome;
257
533
  const ack = `Committed: source page + ${committed.entitiesCreated.length} new entit${
258
534
  committed.entitiesCreated.length === 1 ? "y" : "ies"
259
535
  }, ${committed.conceptsCreated.length} new concept${
@@ -270,7 +546,7 @@ export async function runIngestSynthesis(
270
546
  model,
271
547
  apiKey,
272
548
  headers,
273
- systemPrompt: INGEST_SYSTEM,
549
+ systemPrompt,
274
550
  userPrompt,
275
551
  tools: [commitTool as AgentTool],
276
552
  signal,