ennodia 0.1.1 → 0.2.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 (46) hide show
  1. package/CONTRIBUTING.md +60 -36
  2. package/README.md +74 -58
  3. package/bench/README.md +7 -5
  4. package/bench/bug-recall/run.ts +12 -4
  5. package/bin/ennodia +23 -2
  6. package/docs/assets/agents/mcp.svg +1 -1
  7. package/docs/assets/orchestration-pipeline.svg +4 -3
  8. package/docs/comparisons/agent-frameworks.md +15 -10
  9. package/docs/comparisons/autogen.md +20 -15
  10. package/docs/comparisons/chathub.md +15 -14
  11. package/docs/comparisons/index.md +30 -22
  12. package/docs/comparisons/langgraph.md +19 -12
  13. package/docs/comparisons/mixture-of-agents.md +20 -17
  14. package/docs/comparisons/model-merging.md +7 -5
  15. package/docs/comparisons/openrouter.md +30 -22
  16. package/docs/concepts/compositional-audits.md +16 -13
  17. package/docs/concepts/data-governance.md +31 -15
  18. package/docs/concepts/how-ennodia-works.md +48 -22
  19. package/docs/concepts/interfaces-and-core.md +19 -6
  20. package/docs/concepts/second-opinions.md +20 -20
  21. package/docs/getting-started.md +55 -27
  22. package/docs/guides/agent-skills.md +16 -13
  23. package/docs/guides/budgets-and-limits.md +30 -20
  24. package/docs/guides/running-better-audits.md +50 -44
  25. package/docs/index.md +60 -37
  26. package/docs/install.md +41 -32
  27. package/docs/reference/benchmarks.md +6 -4
  28. package/docs/reference/controlled-english.md +130 -0
  29. package/docs/reference/ennodia-io.md +28 -22
  30. package/docs/reference/mcp-tools.md +244 -87
  31. package/docs/reference/supported-harnesses.md +9 -9
  32. package/package.json +7 -5
  33. package/src/advisor.ts +685 -0
  34. package/src/budget.ts +25 -6
  35. package/src/cli.ts +1 -1
  36. package/src/compare.ts +248 -52
  37. package/src/compositional.ts +18 -0
  38. package/src/core.ts +547 -16
  39. package/src/harnesses.ts +33 -2
  40. package/src/index.ts +38 -0
  41. package/src/plan-advice.ts +601 -0
  42. package/src/runs.ts +74 -9
  43. package/src/server.ts +312 -46
  44. package/src/skills.ts +131 -11
  45. package/src/tasks.ts +192 -22
  46. package/src/version.ts +1 -1
package/src/server.ts CHANGED
@@ -152,6 +152,223 @@ export function createEnnodiaServer(core: EnnodiaCore = defaultCore): McpServer
152
152
  },
153
153
  );
154
154
 
155
+ server.registerTool(
156
+ "ennodia_start_plan_advice",
157
+ {
158
+ title: "Start Ennodia Plan Advisor",
159
+ description:
160
+ "Ask one Advisor to propose a bounded team of harness, model, and skill assignments. This starts only the Advisor task: its output is strict, inert plan data that Ennodia validates and never executes automatically. Poll ennodia_get_plan_advice, then explicitly call ennodia_start_advised_plan with the returned digest.",
161
+ inputSchema: {
162
+ prompt: z
163
+ .string()
164
+ .min(1)
165
+ .describe("The work for which the Advisor should propose a team."),
166
+ cwd: z
167
+ .string()
168
+ .optional()
169
+ .describe(
170
+ "Optional target working directory used only to discover available skills. The Advisor task runs in a separate empty temporary directory.",
171
+ ),
172
+ refresh: z
173
+ .boolean()
174
+ .default(false)
175
+ .describe(
176
+ "Re-scan harnesses before freezing the Advisor inventory snapshot.",
177
+ ),
178
+ advisorHarnessId: z
179
+ .string()
180
+ .optional()
181
+ .describe(
182
+ `Harness to run the Plan Advisor. Omit to use the default priority: ${comparePriorityText}.`,
183
+ ),
184
+ advisorModel: z
185
+ .string()
186
+ .optional()
187
+ .describe("Optional model override for the Plan Advisor task."),
188
+ allowedHarnessIds: z
189
+ .array(z.string().min(1))
190
+ .min(1)
191
+ .max(64)
192
+ .optional()
193
+ .describe(
194
+ "Worker harness IDs the Advisor may select. Omit to allow every runnable public Ennodia adapter.",
195
+ ),
196
+ allowedModels: z
197
+ .record(z.string().min(1), z.array(z.string().min(1)).max(128))
198
+ .optional()
199
+ .describe(
200
+ "Exact caller-approved worker model IDs by harness. A model omitted by the Advisor uses that harness's default.",
201
+ ),
202
+ maxSlices: z
203
+ .number()
204
+ .int()
205
+ .min(1)
206
+ .max(64)
207
+ .optional()
208
+ .describe("Maximum explicit worker slices the Advisor may propose."),
209
+ maxSkillsPerSlice: z
210
+ .number()
211
+ .int()
212
+ .min(0)
213
+ .max(64)
214
+ .optional()
215
+ .describe("Maximum installed native skills assigned to one slice."),
216
+ maxTotalSkillAssignments: z
217
+ .number()
218
+ .int()
219
+ .min(0)
220
+ .max(1_024)
221
+ .optional()
222
+ .describe("Maximum skill assignments across the complete plan."),
223
+ timeoutMs: z
224
+ .number()
225
+ .int()
226
+ .positive()
227
+ .max(60 * 60 * 1000)
228
+ .optional()
229
+ .describe(
230
+ "Timeout for the single Plan Advisor task in milliseconds, capped at one hour.",
231
+ ),
232
+ budget: budgetSchema.optional(),
233
+ },
234
+ },
235
+ async (input) => jsonResult(await core.startPlanAdvice(input)),
236
+ );
237
+
238
+ server.registerTool(
239
+ "ennodia_list_plan_advice",
240
+ {
241
+ title: "List Ennodia plan advice",
242
+ description:
243
+ "List recent Plan Advisor requests started by this Ennodia MCP server process.",
244
+ inputSchema: {
245
+ includeProposal: z
246
+ .boolean()
247
+ .default(false)
248
+ .describe("Include the untrusted model-authored proposal when available."),
249
+ includePlan: z
250
+ .boolean()
251
+ .default(false)
252
+ .describe("Include the validated inert execution plan when ready."),
253
+ includeEvents: z
254
+ .boolean()
255
+ .default(false)
256
+ .describe("Include bounded Plan Advisor event history."),
257
+ maxEvents: z
258
+ .number()
259
+ .int()
260
+ .nonnegative()
261
+ .max(300)
262
+ .default(25)
263
+ .describe(
264
+ "Maximum Plan Advisor events per item when includeEvents is true.",
265
+ ),
266
+ },
267
+ },
268
+ async (options) => jsonResult(core.listPlanAdvice(options)),
269
+ );
270
+
271
+ server.registerTool(
272
+ "ennodia_get_plan_advice",
273
+ {
274
+ title: "Get Ennodia plan advice",
275
+ description:
276
+ "Inspect Plan Advisor status, validation issues, frozen inventory identity, proposal, validated plan, digest, budgets, events, and ETA. A plan can be launched only when status is ready. An authorized plan has status consumed and cannot run again.",
277
+ inputSchema: {
278
+ adviceId: z
279
+ .string()
280
+ .min(1)
281
+ .describe("Plan advice ID returned by ennodia_start_plan_advice."),
282
+ includeProposal: z
283
+ .boolean()
284
+ .default(true)
285
+ .describe("Include the untrusted model-authored proposal when available."),
286
+ includePlan: z
287
+ .boolean()
288
+ .default(true)
289
+ .describe("Include the validated inert execution plan when ready."),
290
+ includeEvents: z
291
+ .boolean()
292
+ .default(true)
293
+ .describe("Include bounded Plan Advisor event history."),
294
+ maxEvents: z
295
+ .number()
296
+ .int()
297
+ .nonnegative()
298
+ .max(300)
299
+ .default(100)
300
+ .describe("Maximum Plan Advisor events to include. Use 0 to omit events."),
301
+ },
302
+ },
303
+ async ({ adviceId, ...options }) => {
304
+ const advice = core.getPlanAdvice(adviceId, options);
305
+ if (!advice) {
306
+ throw new Error(`Unknown plan advice: ${adviceId}`);
307
+ }
308
+
309
+ return jsonResult(advice);
310
+ },
311
+ );
312
+
313
+ server.registerTool(
314
+ "ennodia_cancel_plan_advice",
315
+ {
316
+ title: "Cancel Ennodia plan advice",
317
+ description: "Cancel a running Plan Advisor request and its Advisor task.",
318
+ inputSchema: {
319
+ adviceId: z
320
+ .string()
321
+ .min(1)
322
+ .describe("Plan advice ID returned by ennodia_start_plan_advice."),
323
+ },
324
+ },
325
+ async ({ adviceId }) => jsonResult(core.cancelPlanAdvice(adviceId)),
326
+ );
327
+
328
+ server.registerTool(
329
+ "ennodia_start_advised_plan",
330
+ {
331
+ title: "Start an Ennodia advised plan",
332
+ description:
333
+ "Explicitly launch the exact validated worker plan from a ready Plan Advisor request. Ennodia re-checks the caller-supplied digest, current harness and skill inventory, caller allowlisted model assignments, and budget before starting the first worker task; any mismatch fails closed with zero launches. A plan is single-use after authorization. Provider model availability remains unverified until the harness runs.",
334
+ inputSchema: {
335
+ adviceId: z
336
+ .string()
337
+ .min(1)
338
+ .describe("Ready plan advice ID returned by ennodia_start_plan_advice."),
339
+ expectedPlanDigest: z
340
+ .string()
341
+ .min(1)
342
+ .describe(
343
+ "Exact planDigest returned by ennodia_get_plan_advice for explicit, stale-plan-safe execution.",
344
+ ),
345
+ cwd: z
346
+ .string()
347
+ .optional()
348
+ .describe(
349
+ "Working directory for worker tasks. Omit to reuse the directory whose skills were inventoried.",
350
+ ),
351
+ isolateCwd: z
352
+ .boolean()
353
+ .optional()
354
+ .describe(
355
+ "Run each worker against an ephemeral isolated copy of cwd so parallel writes cannot clobber each other. Isolation fails before task spawn if the copied tree contains a symbolic link. The execution path is reported as the task cwd for traceability and deleted when the task becomes terminal.",
356
+ ),
357
+ timeoutMs: z
358
+ .number()
359
+ .int()
360
+ .positive()
361
+ .max(60 * 60 * 1000)
362
+ .optional()
363
+ .describe(
364
+ "Per-worker timeout in milliseconds, capped at one hour.",
365
+ ),
366
+ budget: budgetSchema.optional(),
367
+ },
368
+ },
369
+ async (input) => jsonResult(await core.startAdvisedPlan(input)),
370
+ );
371
+
155
372
  server.registerTool(
156
373
  "ennodia_estimate_budget",
157
374
  {
@@ -180,7 +397,7 @@ export function createEnnodiaServer(core: EnnodiaCore = defaultCore): McpServer
180
397
  .union([z.literal("auto"), z.boolean()])
181
398
  .default("auto")
182
399
  .describe(
183
- "Whether Compare should be included in the estimate. auto follows the planner.",
400
+ "Whether the Judge + Result Advisor comparison should be included in the estimate. auto follows the planner.",
184
401
  ),
185
402
  refresh: z
186
403
  .boolean()
@@ -193,7 +410,7 @@ export function createEnnodiaServer(core: EnnodiaCore = defaultCore): McpServer
193
410
  .max(200_000)
194
411
  .optional()
195
412
  .describe(
196
- "Maximum characters from each successful task output assumed for Compare input.",
413
+ "Maximum characters from each successful task output assumed for Judge + Result Advisor input.",
197
414
  ),
198
415
  budget: budgetSchema.optional(),
199
416
  },
@@ -206,13 +423,13 @@ export function createEnnodiaServer(core: EnnodiaCore = defaultCore): McpServer
206
423
  {
207
424
  title: "Estimate compositional Ennodia budget",
208
425
  description:
209
- "Resolve focused compositional slices to harnesses and estimate child-task and Compare budget without starting child processes.",
426
+ "Resolve focused compositional slices to harnesses and estimate child-task and later Judge + Result Advisor budget without starting child processes.",
210
427
  inputSchema: {
211
428
  prompt: z
212
429
  .string()
213
430
  .min(1)
214
431
  .describe(
215
- "Overall question or synthesis goal that every slice belongs to.",
432
+ "Overall task or question that every slice belongs to.",
216
433
  ),
217
434
  slices: z
218
435
  .array(compositionalSliceSchema)
@@ -235,13 +452,13 @@ export function createEnnodiaServer(core: EnnodiaCore = defaultCore): McpServer
235
452
  .array(z.string())
236
453
  .optional()
237
454
  .describe(
238
- "Optional list of installed native skill IDs to validate against every selected slice harness.",
455
+ "Default installed native skill IDs for slices that omit slice.skillIds. A slice-level list, including [], replaces this default.",
239
456
  ),
240
457
  includeCompareEstimate: z
241
458
  .boolean()
242
459
  .default(true)
243
460
  .describe(
244
- "Include a later Compare pass in the returned preflight budget estimate.",
461
+ "Include a later Judge + Result Advisor pass in the returned preflight budget estimate.",
245
462
  ),
246
463
  maxOutputChars: z
247
464
  .number()
@@ -250,7 +467,7 @@ export function createEnnodiaServer(core: EnnodiaCore = defaultCore): McpServer
250
467
  .max(200_000)
251
468
  .optional()
252
469
  .describe(
253
- "Maximum characters per successful slice output assumed for a later Compare estimate.",
470
+ "Maximum characters per successful slice output assumed for a later Judge + Result Advisor estimate.",
254
471
  ),
255
472
  budget: budgetSchema.optional(),
256
473
  },
@@ -263,7 +480,7 @@ export function createEnnodiaServer(core: EnnodiaCore = defaultCore): McpServer
263
480
  {
264
481
  title: "Start an Ennodia task",
265
482
  description:
266
- "Start raw child harness tasks without run-level Compare or final synthesis. Use for debugging, direct harness calls, or manual Compare workflows; prefer ennodia_run for normal end-to-end use.",
483
+ "Start raw child harness tasks without a run-level Judge + Result Advisor pass. Use for debugging, direct harness calls, or manual comparison workflows; prefer ennodia_run for normal end-to-end use.",
267
484
  inputSchema: {
268
485
  prompt: z
269
486
  .string()
@@ -286,6 +503,12 @@ export function createEnnodiaServer(core: EnnodiaCore = defaultCore): McpServer
286
503
  .string()
287
504
  .optional()
288
505
  .describe("Working directory for child harness commands."),
506
+ isolateCwd: z
507
+ .boolean()
508
+ .optional()
509
+ .describe(
510
+ "Run each task against an ephemeral isolated copy of cwd instead of cwd itself. Use this whenever starting more than one task against the same cwd (e.g. comparing skills or harnesses), so concurrent file writes cannot clobber each other. Isolation fails before task spawn if the copied tree contains a symbolic link. The reported task cwd is an execution trace; its temporary directory is deleted when the task becomes terminal.",
511
+ ),
289
512
  model: z
290
513
  .string()
291
514
  .optional()
@@ -320,13 +543,13 @@ export function createEnnodiaServer(core: EnnodiaCore = defaultCore): McpServer
320
543
  {
321
544
  title: "Start compositional Ennodia tasks",
322
545
  description:
323
- "Start one focused child task per slice for compositional reviews. Poll the returned task IDs, then pass useful completed task IDs to ennodia_start_compare for synthesis.",
546
+ "Start one focused child task per slice for compositional reviews. Poll the returned task IDs, then pass useful completed task IDs to ennodia_start_compare for a Judge + Result Advisor comparison.",
324
547
  inputSchema: {
325
548
  prompt: z
326
549
  .string()
327
550
  .min(1)
328
551
  .describe(
329
- "Overall question or synthesis goal that every slice belongs to.",
552
+ "Overall task or question that every slice belongs to.",
330
553
  ),
331
554
  slices: z
332
555
  .array(compositionalSliceSchema)
@@ -339,6 +562,12 @@ export function createEnnodiaServer(core: EnnodiaCore = defaultCore): McpServer
339
562
  .string()
340
563
  .optional()
341
564
  .describe("Working directory for child harness commands."),
565
+ isolateCwd: z
566
+ .boolean()
567
+ .optional()
568
+ .describe(
569
+ "Run each slice's task against an ephemeral isolated copy of cwd instead of cwd itself, so slices that write files cannot clobber each other. Isolation fails before task spawn if the copied tree contains a symbolic link. The reported task cwd is an execution trace; its temporary directory is deleted when the task becomes terminal.",
570
+ ),
342
571
  timeoutMs: z
343
572
  .number()
344
573
  .int()
@@ -356,13 +585,13 @@ export function createEnnodiaServer(core: EnnodiaCore = defaultCore): McpServer
356
585
  .array(z.string())
357
586
  .optional()
358
587
  .describe(
359
- "Optional list of installed native skill IDs to ask every selected slice harness to use.",
588
+ "Default installed native skill IDs for slices that omit slice.skillIds. A slice-level list, including [], replaces this default.",
360
589
  ),
361
590
  includeCompareEstimate: z
362
591
  .boolean()
363
592
  .default(true)
364
593
  .describe(
365
- "Include a later Compare pass in the returned preflight budget estimate.",
594
+ "Include a later Judge + Result Advisor pass in the returned preflight budget estimate.",
366
595
  ),
367
596
  maxOutputChars: z
368
597
  .number()
@@ -371,7 +600,7 @@ export function createEnnodiaServer(core: EnnodiaCore = defaultCore): McpServer
371
600
  .max(200_000)
372
601
  .optional()
373
602
  .describe(
374
- "Maximum characters per successful slice output assumed for a later Compare estimate.",
603
+ "Maximum characters per successful slice output assumed for a later Judge + Result Advisor estimate.",
375
604
  ),
376
605
  budget: budgetSchema.optional(),
377
606
  },
@@ -417,12 +646,18 @@ export function createEnnodiaServer(core: EnnodiaCore = defaultCore): McpServer
417
646
  .union([z.literal("auto"), z.boolean()])
418
647
  .default("auto")
419
648
  .describe(
420
- "Whether to run Compare after tasks finish. auto compares when the planner sees value and at least two tasks produce output.",
649
+ "Whether to run the Judge + Result Advisor after tasks finish. auto compares when the planner sees value and at least two tasks produce output.",
421
650
  ),
422
651
  cwd: z
423
652
  .string()
424
653
  .optional()
425
654
  .describe("Working directory for child harness commands."),
655
+ isolateCwd: z
656
+ .boolean()
657
+ .optional()
658
+ .describe(
659
+ "Run each selected harness against an ephemeral isolated copy of cwd instead of cwd itself. Use this in parallel mode or whenever comparing skills/harnesses against the same cwd, so concurrent file writes cannot clobber each other. Isolation fails before task spawn if the copied tree contains a symbolic link. The reported task cwd is an execution trace; its temporary directory is deleted when the task becomes terminal.",
660
+ ),
426
661
  model: z
427
662
  .string()
428
663
  .optional()
@@ -444,22 +679,34 @@ export function createEnnodiaServer(core: EnnodiaCore = defaultCore): McpServer
444
679
  .string()
445
680
  .optional()
446
681
  .describe(
447
- `Harness to use as the Compare judge. Omit to use the default priority: ${comparePriorityText}.`,
682
+ `Harness to use as the comparison Judge. Omit to use the default priority: ${comparePriorityText}.`,
448
683
  ),
449
684
  judgeModel: z
450
685
  .string()
451
686
  .optional()
452
- .describe("Optional model override for the Compare judge."),
687
+ .describe("Optional model override for the comparison Judge."),
688
+ advisorHarnessId: z
689
+ .string()
690
+ .optional()
691
+ .describe(
692
+ "Harness that runs the Result Advisor after the Judge. Defaults to the judge harness selection.",
693
+ ),
694
+ advisorModel: z
695
+ .string()
696
+ .optional()
697
+ .describe("Optional model override for the Result Advisor."),
453
698
  synthesizerHarnessId: z
454
699
  .string()
455
700
  .optional()
456
701
  .describe(
457
- "Harness that writes the final synthesized answer. Defaults to the judge harness selection.",
702
+ "Deprecated alias for advisorHarnessId. Do not send both fields with different values.",
458
703
  ),
459
704
  synthesizerModel: z
460
705
  .string()
461
706
  .optional()
462
- .describe("Optional model override for the Compare synthesizer."),
707
+ .describe(
708
+ "Deprecated alias for advisorModel. Do not send both fields with different values.",
709
+ ),
463
710
  maxOutputChars: z
464
711
  .number()
465
712
  .int()
@@ -467,7 +714,7 @@ export function createEnnodiaServer(core: EnnodiaCore = defaultCore): McpServer
467
714
  .max(200_000)
468
715
  .optional()
469
716
  .describe(
470
- "Maximum characters from each successful task output to pass into Compare. Use 0 to suppress candidate text.",
717
+ "Maximum characters from each successful task output to pass into the Judge + Result Advisor workflow. Use 0 to suppress candidate text.",
471
718
  ),
472
719
  skillIds: z
473
720
  .array(z.string())
@@ -517,7 +764,7 @@ export function createEnnodiaServer(core: EnnodiaCore = defaultCore): McpServer
517
764
  {
518
765
  title: "List durable Ennodia run history",
519
766
  description:
520
- "Read terminal run receipts persisted under the local Ennodia history directory. Use after a restart to inspect previous final answers and Compare disagreement analysis.",
767
+ "Read terminal run receipts persisted under the local Ennodia history directory. Use after a restart to inspect previous final answers and Judge disagreement analysis.",
521
768
  inputSchema: {
522
769
  limit: z
523
770
  .number()
@@ -536,7 +783,7 @@ export function createEnnodiaServer(core: EnnodiaCore = defaultCore): McpServer
536
783
  {
537
784
  title: "Get Ennodia run",
538
785
  description:
539
- "Inspect run status, selected harnesses, child task IDs, Compare ID, final answer, events, and ETA.",
786
+ "Inspect run status, selected harnesses, child task IDs, comparison ID, final Advisor answer, events, and ETA.",
540
787
  inputSchema: {
541
788
  runId: z.string().min(1).describe("Run ID returned by ennodia_run."),
542
789
  includeEvents: z
@@ -575,7 +822,8 @@ export function createEnnodiaServer(core: EnnodiaCore = defaultCore): McpServer
575
822
  "ennodia_cancel_run",
576
823
  {
577
824
  title: "Cancel Ennodia run",
578
- description: "Cancel a high-level run and any active child task or Compare.",
825
+ description:
826
+ "Cancel a high-level run and any active child task, Judge, or Result Advisor.",
579
827
  inputSchema: {
580
828
  runId: z.string().min(1).describe("Run ID returned by ennodia_run."),
581
829
  },
@@ -623,7 +871,7 @@ export function createEnnodiaServer(core: EnnodiaCore = defaultCore): McpServer
623
871
  {
624
872
  title: "Get compositional Ennodia status",
625
873
  description:
626
- "Inspect a batch of compositional shard tasks, group running and terminal states, and return Compare-ready successful task IDs.",
874
+ "Inspect a batch of compositional shard tasks, group running and terminal states, and return successful task IDs ready for Judge + Result Advisor comparison.",
627
875
  inputSchema: {
628
876
  taskIds: z
629
877
  .array(z.string().min(1).describe("Task ID returned by a compositional start."))
@@ -634,7 +882,7 @@ export function createEnnodiaServer(core: EnnodiaCore = defaultCore): McpServer
634
882
  .string()
635
883
  .optional()
636
884
  .describe(
637
- "Optional synthesis prompt to include in the returned compareNext object.",
885
+ "Optional comparison prompt to include in the returned compareNext object.",
638
886
  ),
639
887
  minSuccessfulTasksForCompare: z
640
888
  .number()
@@ -678,9 +926,9 @@ export function createEnnodiaServer(core: EnnodiaCore = defaultCore): McpServer
678
926
  server.registerTool(
679
927
  "ennodia_start_compare",
680
928
  {
681
- title: "Start Ennodia Compare",
929
+ title: "Start Ennodia Judge + Result Advisor",
682
930
  description:
683
- "Compare completed Ennodia task outputs or caller-supplied responses. Runs a judge pass, then a synthesizer pass, and returns a Compare ID to inspect with ennodia_get_compare.",
931
+ "Compare completed Ennodia task outputs or caller-supplied responses. The Judge maps agreements, contradictions, and risks; the Result Advisor then combines that analysis into the final answer. Returns a Compare ID to inspect with ennodia_get_compare.",
684
932
  inputSchema: {
685
933
  prompt: z
686
934
  .string()
@@ -718,26 +966,38 @@ export function createEnnodiaServer(core: EnnodiaCore = defaultCore): McpServer
718
966
  .string()
719
967
  .optional()
720
968
  .describe(
721
- `Harness to use as the Compare judge. Omit to use the default priority: ${comparePriorityText}.`,
969
+ `Harness to use as the comparison Judge. Omit to use the default priority: ${comparePriorityText}.`,
722
970
  ),
723
971
  judgeModel: z
724
972
  .string()
725
973
  .optional()
726
- .describe("Optional model override for the Compare judge."),
974
+ .describe("Optional model override for the comparison Judge."),
975
+ advisorHarnessId: z
976
+ .string()
977
+ .optional()
978
+ .describe(
979
+ "Harness that runs the Result Advisor after the Judge. Defaults to the judge harness selection.",
980
+ ),
981
+ advisorModel: z
982
+ .string()
983
+ .optional()
984
+ .describe("Optional model override for the Result Advisor."),
727
985
  synthesizerHarnessId: z
728
986
  .string()
729
987
  .optional()
730
988
  .describe(
731
- "Harness that writes the final synthesized answer. Defaults to the judge harness selection.",
989
+ "Deprecated alias for advisorHarnessId. Do not send both fields with different values.",
732
990
  ),
733
991
  synthesizerModel: z
734
992
  .string()
735
993
  .optional()
736
- .describe("Optional model override for the Compare synthesizer."),
994
+ .describe(
995
+ "Deprecated alias for advisorModel. Do not send both fields with different values.",
996
+ ),
737
997
  cwd: z
738
998
  .string()
739
999
  .optional()
740
- .describe("Working directory for judge and synthesizer child tasks."),
1000
+ .describe("Working directory for Judge and Result Advisor child tasks."),
741
1001
  timeoutMs: z
742
1002
  .number()
743
1003
  .int()
@@ -745,7 +1005,7 @@ export function createEnnodiaServer(core: EnnodiaCore = defaultCore): McpServer
745
1005
  .max(60 * 60 * 1000)
746
1006
  .optional()
747
1007
  .describe(
748
- "Per-child timeout for judge and synthesizer tasks in milliseconds, capped at one hour.",
1008
+ "Per-child timeout for Judge and Result Advisor tasks in milliseconds, capped at one hour.",
749
1009
  ),
750
1010
  maxOutputChars: z
751
1011
  .number()
@@ -754,7 +1014,7 @@ export function createEnnodiaServer(core: EnnodiaCore = defaultCore): McpServer
754
1014
  .max(200_000)
755
1015
  .optional()
756
1016
  .describe(
757
- "Maximum characters from each task output to include as a Compare candidate. Use 0 to suppress task output text.",
1017
+ "Maximum characters from each task output to include as a comparison candidate. Use 0 to suppress task output text.",
758
1018
  ),
759
1019
  budget: budgetSchema.optional(),
760
1020
  },
@@ -765,17 +1025,18 @@ export function createEnnodiaServer(core: EnnodiaCore = defaultCore): McpServer
765
1025
  server.registerTool(
766
1026
  "ennodia_list_compares",
767
1027
  {
768
- title: "List Ennodia compares",
769
- description: "List Compare runs started by this Ennodia MCP server process.",
1028
+ title: "List Ennodia Judge + Advisor comparisons",
1029
+ description:
1030
+ "List Judge + Result Advisor comparisons started by this Ennodia MCP server process.",
770
1031
  inputSchema: {
771
1032
  includeCandidates: z
772
1033
  .boolean()
773
1034
  .default(false)
774
- .describe("Include bounded candidate response previews for each Compare."),
1035
+ .describe("Include bounded candidate response previews for each comparison."),
775
1036
  includeEvents: z
776
1037
  .boolean()
777
1038
  .default(false)
778
- .describe("Include bounded Compare event history."),
1039
+ .describe("Include bounded Judge + Result Advisor event history."),
779
1040
  maxCandidateChars: z
780
1041
  .number()
781
1042
  .int()
@@ -792,7 +1053,7 @@ export function createEnnodiaServer(core: EnnodiaCore = defaultCore): McpServer
792
1053
  .max(300)
793
1054
  .default(25)
794
1055
  .describe(
795
- "Maximum Compare events to include when includeEvents is true.",
1056
+ "Maximum comparison events to include when includeEvents is true.",
796
1057
  ),
797
1058
  },
798
1059
  },
@@ -802,9 +1063,9 @@ export function createEnnodiaServer(core: EnnodiaCore = defaultCore): McpServer
802
1063
  server.registerTool(
803
1064
  "ennodia_get_compare",
804
1065
  {
805
- title: "Get Ennodia Compare",
1066
+ title: "Get Ennodia Judge + Result Advisor comparison",
806
1067
  description:
807
- "Inspect Compare status, candidate inputs, judge analysis, final synthesis, child task IDs, and ETA.",
1068
+ "Inspect comparison status, candidate inputs, Judge analysis, Result Advisor answer, child task IDs, degradation state, and ETA.",
808
1069
  inputSchema: {
809
1070
  compareId: z
810
1071
  .string()
@@ -817,7 +1078,7 @@ export function createEnnodiaServer(core: EnnodiaCore = defaultCore): McpServer
817
1078
  includeEvents: z
818
1079
  .boolean()
819
1080
  .default(true)
820
- .describe("Include bounded Compare event history."),
1081
+ .describe("Include bounded Judge + Result Advisor event history."),
821
1082
  maxCandidateChars: z
822
1083
  .number()
823
1084
  .int()
@@ -833,7 +1094,7 @@ export function createEnnodiaServer(core: EnnodiaCore = defaultCore): McpServer
833
1094
  .nonnegative()
834
1095
  .max(300)
835
1096
  .default(100)
836
- .describe("Maximum Compare events to include. Use 0 to omit events."),
1097
+ .describe("Maximum comparison events to include. Use 0 to omit events."),
837
1098
  },
838
1099
  },
839
1100
  async ({ compareId, ...options }) => {
@@ -849,8 +1110,9 @@ export function createEnnodiaServer(core: EnnodiaCore = defaultCore): McpServer
849
1110
  server.registerTool(
850
1111
  "ennodia_cancel_compare",
851
1112
  {
852
- title: "Cancel Ennodia Compare",
853
- description: "Cancel a running Compare and its active child task.",
1113
+ title: "Cancel Ennodia Judge + Result Advisor comparison",
1114
+ description:
1115
+ "Cancel a running Judge + Result Advisor comparison and its active child task.",
854
1116
  inputSchema: {
855
1117
  compareId: z
856
1118
  .string()
@@ -870,7 +1132,9 @@ export function createEnnodiaServer(core: EnnodiaCore = defaultCore): McpServer
870
1132
  taskId: z
871
1133
  .string()
872
1134
  .min(1)
873
- .describe("Task ID returned by ennodia_start, ennodia_run, or Compare."),
1135
+ .describe(
1136
+ "Task ID returned by a raw, compositional, advised-plan, run, Plan Advisor, or Compare start.",
1137
+ ),
874
1138
  includeOutput: z
875
1139
  .boolean()
876
1140
  .default(true)
@@ -916,7 +1180,9 @@ export function createEnnodiaServer(core: EnnodiaCore = defaultCore): McpServer
916
1180
  taskId: z
917
1181
  .string()
918
1182
  .min(1)
919
- .describe("Task ID returned by ennodia_start, ennodia_run, or Compare."),
1183
+ .describe(
1184
+ "Task ID returned by a raw, compositional, advised-plan, run, Plan Advisor, or Compare start.",
1185
+ ),
920
1186
  },
921
1187
  },
922
1188
  async ({ taskId }) => jsonResult(core.cancelTask(taskId)),