@agenteer/stdlib 1.0.0-rc.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 (118) hide show
  1. package/README.md +98 -0
  2. package/dist/.tsbuildinfo +1 -0
  3. package/dist/context/context_curator.d.ts +115 -0
  4. package/dist/context/context_curator.d.ts.map +1 -0
  5. package/dist/context/context_curator.js +132 -0
  6. package/dist/context/context_curator.js.map +1 -0
  7. package/dist/humans/approval_gate.d.ts +82 -0
  8. package/dist/humans/approval_gate.d.ts.map +1 -0
  9. package/dist/humans/approval_gate.js +76 -0
  10. package/dist/humans/approval_gate.js.map +1 -0
  11. package/dist/humans/ask_user.d.ts +79 -0
  12. package/dist/humans/ask_user.d.ts.map +1 -0
  13. package/dist/humans/ask_user.js +101 -0
  14. package/dist/humans/ask_user.js.map +1 -0
  15. package/dist/index.d.ts +49 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +93 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/meta/cross_check.d.ts +96 -0
  20. package/dist/meta/cross_check.d.ts.map +1 -0
  21. package/dist/meta/cross_check.js +165 -0
  22. package/dist/meta/cross_check.js.map +1 -0
  23. package/dist/meta/judge_with_stripped_ctx.d.ts +75 -0
  24. package/dist/meta/judge_with_stripped_ctx.d.ts.map +1 -0
  25. package/dist/meta/judge_with_stripped_ctx.js +151 -0
  26. package/dist/meta/judge_with_stripped_ctx.js.map +1 -0
  27. package/dist/meta/parallel_fanout.d.ts +83 -0
  28. package/dist/meta/parallel_fanout.d.ts.map +1 -0
  29. package/dist/meta/parallel_fanout.js +120 -0
  30. package/dist/meta/parallel_fanout.js.map +1 -0
  31. package/dist/meta/repair_loop.d.ts +90 -0
  32. package/dist/meta/repair_loop.d.ts.map +1 -0
  33. package/dist/meta/repair_loop.js +230 -0
  34. package/dist/meta/repair_loop.js.map +1 -0
  35. package/dist/planner/default_planner.d.ts +88 -0
  36. package/dist/planner/default_planner.d.ts.map +1 -0
  37. package/dist/planner/default_planner.js +156 -0
  38. package/dist/planner/default_planner.js.map +1 -0
  39. package/dist/primitives/file_read.d.ts +60 -0
  40. package/dist/primitives/file_read.d.ts.map +1 -0
  41. package/dist/primitives/file_read.js +68 -0
  42. package/dist/primitives/file_read.js.map +1 -0
  43. package/dist/primitives/file_write.d.ts +60 -0
  44. package/dist/primitives/file_write.d.ts.map +1 -0
  45. package/dist/primitives/file_write.js +66 -0
  46. package/dist/primitives/file_write.js.map +1 -0
  47. package/dist/primitives/llm_call.d.ts +85 -0
  48. package/dist/primitives/llm_call.d.ts.map +1 -0
  49. package/dist/primitives/llm_call.js +99 -0
  50. package/dist/primitives/llm_call.js.map +1 -0
  51. package/dist/primitives/shell_exec.d.ts +66 -0
  52. package/dist/primitives/shell_exec.d.ts.map +1 -0
  53. package/dist/primitives/shell_exec.js +106 -0
  54. package/dist/primitives/shell_exec.js.map +1 -0
  55. package/dist/primitives/tool_call.d.ts +62 -0
  56. package/dist/primitives/tool_call.d.ts.map +1 -0
  57. package/dist/primitives/tool_call.js +69 -0
  58. package/dist/primitives/tool_call.js.map +1 -0
  59. package/dist/shared/capture.d.ts +34 -0
  60. package/dist/shared/capture.d.ts.map +1 -0
  61. package/dist/shared/capture.js +124 -0
  62. package/dist/shared/capture.js.map +1 -0
  63. package/dist/shared/ctx.d.ts +14 -0
  64. package/dist/shared/ctx.d.ts.map +1 -0
  65. package/dist/shared/ctx.js +48 -0
  66. package/dist/shared/ctx.js.map +1 -0
  67. package/dist/shared/index.d.ts +4 -0
  68. package/dist/shared/index.d.ts.map +1 -0
  69. package/dist/shared/index.js +4 -0
  70. package/dist/shared/index.js.map +1 -0
  71. package/dist/shared/validator.d.ts +47 -0
  72. package/dist/shared/validator.d.ts.map +1 -0
  73. package/dist/shared/validator.js +41 -0
  74. package/dist/shared/validator.js.map +1 -0
  75. package/dist/validators/compile.d.ts +83 -0
  76. package/dist/validators/compile.d.ts.map +1 -0
  77. package/dist/validators/compile.js +126 -0
  78. package/dist/validators/compile.js.map +1 -0
  79. package/dist/validators/json_schema_validate.d.ts +72 -0
  80. package/dist/validators/json_schema_validate.d.ts.map +1 -0
  81. package/dist/validators/json_schema_validate.js +85 -0
  82. package/dist/validators/json_schema_validate.js.map +1 -0
  83. package/dist/validators/regex_check.d.ts +64 -0
  84. package/dist/validators/regex_check.d.ts.map +1 -0
  85. package/dist/validators/regex_check.js +85 -0
  86. package/dist/validators/regex_check.js.map +1 -0
  87. package/dist/validators/test_run.d.ts +74 -0
  88. package/dist/validators/test_run.d.ts.map +1 -0
  89. package/dist/validators/test_run.js +149 -0
  90. package/dist/validators/test_run.js.map +1 -0
  91. package/dist/validators/typecheck.d.ts +61 -0
  92. package/dist/validators/typecheck.d.ts.map +1 -0
  93. package/dist/validators/typecheck.js +89 -0
  94. package/dist/validators/typecheck.js.map +1 -0
  95. package/package.json +61 -0
  96. package/src/context/context_curator.ts +154 -0
  97. package/src/humans/approval_gate.ts +101 -0
  98. package/src/humans/ask_user.ts +137 -0
  99. package/src/index.ts +149 -0
  100. package/src/meta/cross_check.ts +219 -0
  101. package/src/meta/judge_with_stripped_ctx.ts +171 -0
  102. package/src/meta/parallel_fanout.ts +142 -0
  103. package/src/meta/repair_loop.ts +267 -0
  104. package/src/planner/default_planner.ts +182 -0
  105. package/src/primitives/file_read.ts +82 -0
  106. package/src/primitives/file_write.ts +80 -0
  107. package/src/primitives/llm_call.ts +113 -0
  108. package/src/primitives/shell_exec.ts +122 -0
  109. package/src/primitives/tool_call.ts +84 -0
  110. package/src/shared/capture.ts +155 -0
  111. package/src/shared/ctx.ts +45 -0
  112. package/src/shared/index.ts +17 -0
  113. package/src/shared/validator.ts +51 -0
  114. package/src/validators/compile.ts +175 -0
  115. package/src/validators/json_schema_validate.ts +103 -0
  116. package/src/validators/regex_check.ts +107 -0
  117. package/src/validators/test_run.ts +199 -0
  118. package/src/validators/typecheck.ts +121 -0
package/src/index.ts ADDED
@@ -0,0 +1,149 @@
1
+ /**
2
+ * @agenteer/stdlib — v1 standard library (M5 adds ask_user: 19 nodes).
3
+ *
4
+ * Primitives (5): file_read, file_write, shell_exec, llm_call, tool_call
5
+ * Validators (5): compile, test_run, regex_check, typecheck, json_schema_validate
6
+ * Meta (4): parallel_fanout, cross_check, judge_with_stripped_ctx, repair_loop
7
+ * Humans (2): approval_gate, ask_user (M5)
8
+ * Planner (1): default_planner
9
+ * Context (1): context_curator
10
+ *
11
+ * `registerStdlib` registers M2-shape nodes (file_read/write/shell_exec/
12
+ * json_schema_validate/llm_call). Nodes with external dependencies
13
+ * (provider/resolver/tool registry) are exported as factories for the
14
+ * caller to wire and register individually.
15
+ */
16
+
17
+ import type { NodeRegistry } from "@agenteer/core";
18
+
19
+ // Primitives
20
+ import { fileReadFactory, fileReadManifest } from "./primitives/file_read.js";
21
+ import { fileWriteFactory, fileWriteManifest } from "./primitives/file_write.js";
22
+ import { shellExecFactory, shellExecManifest } from "./primitives/shell_exec.js";
23
+ import { llmCallFactory, llmCallManifest } from "./primitives/llm_call.js";
24
+ import { toolCallFactory, toolCallManifest } from "./primitives/tool_call.js";
25
+
26
+ // Validators
27
+ import {
28
+ jsonSchemaValidateFactory,
29
+ jsonSchemaValidateManifest,
30
+ } from "./validators/json_schema_validate.js";
31
+ import { regexCheckFactory, regexCheckManifest } from "./validators/regex_check.js";
32
+ import { compileFactory, compileManifest } from "./validators/compile.js";
33
+ import { testRunFactory, testRunManifest } from "./validators/test_run.js";
34
+ import { typecheckFactory, typecheckManifest } from "./validators/typecheck.js";
35
+
36
+ // Meta
37
+ import { parallelFanoutFactory, parallelFanoutManifest } from "./meta/parallel_fanout.js";
38
+ import { crossCheckFactory, crossCheckManifest } from "./meta/cross_check.js";
39
+ import {
40
+ judgeWithStrippedCtxFactory,
41
+ judgeWithStrippedCtxManifest,
42
+ } from "./meta/judge_with_stripped_ctx.js";
43
+ import { repairLoopFactory, repairLoopManifest } from "./meta/repair_loop.js";
44
+
45
+ // Context, humans, planner
46
+ import { contextCuratorFactory, contextCuratorManifest } from "./context/context_curator.js";
47
+ import { approvalGateFactory, approvalGateManifest } from "./humans/approval_gate.js";
48
+ import { askUserFactory, askUserManifest } from "./humans/ask_user.js";
49
+ import { defaultPlannerFactory, defaultPlannerManifest } from "./planner/default_planner.js";
50
+
51
+ export {
52
+ // Primitives
53
+ fileReadFactory,
54
+ fileReadManifest,
55
+ fileWriteFactory,
56
+ fileWriteManifest,
57
+ shellExecFactory,
58
+ shellExecManifest,
59
+ llmCallFactory,
60
+ llmCallManifest,
61
+ toolCallFactory,
62
+ toolCallManifest,
63
+ // Validators
64
+ jsonSchemaValidateFactory,
65
+ jsonSchemaValidateManifest,
66
+ regexCheckFactory,
67
+ regexCheckManifest,
68
+ compileFactory,
69
+ compileManifest,
70
+ testRunFactory,
71
+ testRunManifest,
72
+ typecheckFactory,
73
+ typecheckManifest,
74
+ // Meta
75
+ parallelFanoutFactory,
76
+ parallelFanoutManifest,
77
+ crossCheckFactory,
78
+ crossCheckManifest,
79
+ judgeWithStrippedCtxFactory,
80
+ judgeWithStrippedCtxManifest,
81
+ repairLoopFactory,
82
+ repairLoopManifest,
83
+ // Context / humans / planner
84
+ contextCuratorFactory,
85
+ contextCuratorManifest,
86
+ approvalGateFactory,
87
+ approvalGateManifest,
88
+ askUserFactory,
89
+ askUserManifest,
90
+ defaultPlannerFactory,
91
+ defaultPlannerManifest,
92
+ };
93
+
94
+ export type { CompileAdapter } from "./validators/compile.js";
95
+ export type { TestAdapter, TestReport } from "./validators/test_run.js";
96
+ export type { ApprovalResolver } from "./humans/approval_gate.js";
97
+ export type { AskUserResolver, AskUserFactoryOptions } from "./humans/ask_user.js";
98
+ export type { ProviderResolver } from "./meta/cross_check.js";
99
+ export * from "./shared/index.js";
100
+
101
+ /**
102
+ * Register M4 nodes that have zero external wiring. Nodes that need a
103
+ * provider resolver, tool registry, approval resolver, or response
104
+ * schema are exported as factories — wire and register them yourself.
105
+ */
106
+ export function registerStdlib(registry: NodeRegistry): void {
107
+ // Primitives (except tool_call / llm_call which need wiring)
108
+ registry.register(fileReadManifest, fileReadFactory);
109
+ registry.register(fileWriteManifest, fileWriteFactory);
110
+ registry.register(shellExecManifest, shellExecFactory);
111
+ registry.register(toolCallManifest, toolCallFactory);
112
+
113
+ // Validators
114
+ registry.register(jsonSchemaValidateManifest, jsonSchemaValidateFactory);
115
+ registry.register(regexCheckManifest, regexCheckFactory);
116
+ registry.register(compileManifest, compileFactory);
117
+ registry.register(testRunManifest, testRunFactory);
118
+ registry.register(typecheckManifest, typecheckFactory);
119
+
120
+ // Meta (parallel_fanout/judge/repair_loop are self-contained)
121
+ registry.register(parallelFanoutManifest, parallelFanoutFactory);
122
+ registry.register(judgeWithStrippedCtxManifest, judgeWithStrippedCtxFactory);
123
+ registry.register(repairLoopManifest, repairLoopFactory);
124
+
125
+ // Context + planner
126
+ registry.register(contextCuratorManifest, contextCuratorFactory);
127
+ registry.register(defaultPlannerManifest, defaultPlannerFactory);
128
+ }
129
+
130
+ export const STDLIB_MANIFEST_IDS = [
131
+ fileReadManifest.id,
132
+ fileWriteManifest.id,
133
+ shellExecManifest.id,
134
+ llmCallManifest.id,
135
+ toolCallManifest.id,
136
+ jsonSchemaValidateManifest.id,
137
+ regexCheckManifest.id,
138
+ compileManifest.id,
139
+ testRunManifest.id,
140
+ typecheckManifest.id,
141
+ parallelFanoutManifest.id,
142
+ crossCheckManifest.id,
143
+ judgeWithStrippedCtxManifest.id,
144
+ repairLoopManifest.id,
145
+ contextCuratorManifest.id,
146
+ approvalGateManifest.id,
147
+ askUserManifest.id,
148
+ defaultPlannerManifest.id,
149
+ ] as const;
@@ -0,0 +1,219 @@
1
+ /**
2
+ * `@agenteer/node-cross-check` — meta-node over `trust.CrossCheckEngine`
3
+ * (sub-plan 03 §11, sub-plan 04 §4.1).
4
+ *
5
+ * Three disagreement policies per master plan's ratified resolution:
6
+ * - `fail` → return `Failed("cross_check_disagreement", retryable: true)`.
7
+ * - `return_both` → return `Output({ primary, secondary, disagreement_keys })`.
8
+ * - `replace_me_with_judge` → return `ReplaceMe(judge_node)` so a
9
+ * different node decides.
10
+ *
11
+ * Retry-on-disagreement uses `ReplaceMe` (sub-plan 00 §7); the engine
12
+ * itself runs one pass.
13
+ */
14
+
15
+ import { z } from "zod";
16
+ import {
17
+ makeManifest,
18
+ type Node,
19
+ type NodeInput,
20
+ type NodeManifest,
21
+ type NodeResult,
22
+ type NodeRuntimeHandle,
23
+ } from "@agenteer/core";
24
+ import {
25
+ CrossCheckEngine,
26
+ ComparatorRegistry,
27
+ DEFAULT_POLICY,
28
+ StructuredProvider,
29
+ type CrossCheckOutcome,
30
+ type CrossCheckPolicy,
31
+ type ProviderLike,
32
+ } from "@agenteer/trust";
33
+
34
+ const MANIFEST: NodeManifest = makeManifest({
35
+ id: "@agenteer/node-cross-check",
36
+ name: "cross_check",
37
+ description:
38
+ "Run the same prompt through two models; surface agreement / disagreement / fallback with policy.",
39
+ determinism: "stochastic",
40
+ required_actions: [],
41
+ dynamic_actions: true,
42
+ // Synthesizes `model:<primary>` at dispatch; secondary is optional.
43
+ dynamic_action_spec: "model:${input.primary_model}",
44
+ tags: ["meta", "llm"],
45
+ side_effects: {
46
+ writes_fs: false,
47
+ network: true,
48
+ mutates_ctx: true,
49
+ emits_ctx_variants: ["artifact.cross_check"],
50
+ },
51
+ });
52
+
53
+ const InputSchema = z.object({
54
+ systemPrompt: z.string(),
55
+ userPrompt: z.string(),
56
+ schemaName: z.string().min(1),
57
+ primary_model: z.string().min(1),
58
+ secondary_model: z.string().optional(),
59
+ /**
60
+ * Policy fallbacks; `on_disagreement` picks the meta-node's response:
61
+ * - fail: `NodeResult.Failed(retryable: true)` for a parent to retry.
62
+ * - return_both: `NodeResult.Output({primary, secondary, disagreement_keys})`.
63
+ * - replace_me_with_judge: `NodeResult.ReplaceMe(judge_manifest_id)`.
64
+ */
65
+ on_disagreement: z
66
+ .enum(["fail", "return_both", "replace_me_with_judge"])
67
+ .default("return_both"),
68
+ judge_manifest_id: z.string().optional(),
69
+ temperature: z.number().min(0).max(2).optional(),
70
+ });
71
+
72
+ const OutputSchema = z.object({
73
+ outcome: z.enum([
74
+ "agreement",
75
+ "disagreement",
76
+ "missing_secondary_skip",
77
+ "missing_secondary_fallback_disagreement",
78
+ ]),
79
+ value: z.unknown().optional(),
80
+ primary: z.unknown().optional(),
81
+ secondary: z.unknown().optional(),
82
+ disagreement_keys: z.array(z.string()).optional(),
83
+ fingerprint: z.string().optional(),
84
+ });
85
+
86
+ type Input = z.input<typeof InputSchema>;
87
+ type Output = z.infer<typeof OutputSchema>;
88
+
89
+ /**
90
+ * Factory takes a resolver that maps model ids → ProviderLike instances.
91
+ * Typical wiring: workflows register their provider adapters once and
92
+ * hand a resolver to the factory.
93
+ */
94
+ export interface ProviderResolver {
95
+ resolve(model_id: string): ProviderLike | null;
96
+ }
97
+
98
+ export function crossCheckFactory(
99
+ resolver: ProviderResolver,
100
+ opts: { policy?: Partial<CrossCheckPolicy>; comparators?: ComparatorRegistry } = {},
101
+ ): () => Node<Input, Output> {
102
+ return () => {
103
+ const policy: CrossCheckPolicy = { ...DEFAULT_POLICY, ...(opts.policy ?? {}) };
104
+ const comparators = opts.comparators ?? new ComparatorRegistry();
105
+ return {
106
+ manifest: MANIFEST,
107
+ inputSchema: InputSchema,
108
+ outputSchema: OutputSchema,
109
+ ctx: [],
110
+ model: null,
111
+ async execute(
112
+ input: NodeInput<Input>,
113
+ _handle: NodeRuntimeHandle,
114
+ ): Promise<NodeResult<Output>> {
115
+ const i = input.original;
116
+ const primary = resolver.resolve(i.primary_model);
117
+ if (!primary) {
118
+ return {
119
+ kind: "failed",
120
+ reason: `no provider registered for primary model '${i.primary_model}'`,
121
+ retryable: false,
122
+ evidence: { verdict: "fail" },
123
+ };
124
+ }
125
+ const secondary = i.secondary_model ? resolver.resolve(i.secondary_model) : undefined;
126
+
127
+ const engine = new CrossCheckEngine(
128
+ new StructuredProvider(primary),
129
+ secondary ? new StructuredProvider(secondary) : undefined,
130
+ policy,
131
+ comparators,
132
+ );
133
+
134
+ const outcome = await engine.run({
135
+ systemPrompt: i.systemPrompt,
136
+ userPrompt: i.userPrompt,
137
+ schema: z.unknown() as unknown as z.ZodType<unknown>,
138
+ schemaName: i.schemaName,
139
+ ...(i.temperature !== undefined ? { temperature: i.temperature } : {}),
140
+ });
141
+
142
+ return handleOutcome(outcome, i);
143
+ },
144
+ };
145
+ };
146
+ }
147
+
148
+ export const crossCheckManifest = MANIFEST;
149
+
150
+ function handleOutcome(
151
+ outcome: CrossCheckOutcome<unknown>,
152
+ input: Input,
153
+ ): NodeResult<Output> {
154
+ if (outcome.kind === "agreement") {
155
+ return {
156
+ kind: "output",
157
+ value: { outcome: "agreement", value: outcome.value },
158
+ evidence: { verdict: "pass" },
159
+ };
160
+ }
161
+ if (outcome.kind === "missing_secondary_skip") {
162
+ return {
163
+ kind: "output",
164
+ value: { outcome: "missing_secondary_skip", value: outcome.value },
165
+ evidence: { verdict: "pass" },
166
+ };
167
+ }
168
+
169
+ const primary = outcome.kind === "missing_secondary_fallback_disagreement"
170
+ ? outcome.primary
171
+ : (outcome as { primary: unknown }).primary;
172
+ const secondary = outcome.kind === "missing_secondary_fallback_disagreement"
173
+ ? outcome.secondFallback
174
+ : (outcome as { secondary: unknown }).secondary;
175
+ const disagreement_keys = (outcome as { disagreementKeys?: string[] }).disagreementKeys ?? [];
176
+ const fingerprint = (outcome as { fingerprint?: string }).fingerprint ?? "";
177
+
178
+ const onDis = input.on_disagreement ?? "return_both";
179
+ if (onDis === "fail") {
180
+ return {
181
+ kind: "failed",
182
+ reason: `cross_check_disagreement:${fingerprint}`,
183
+ retryable: true,
184
+ details: { disagreement_keys, primary, secondary },
185
+ evidence: { verdict: "fail" },
186
+ };
187
+ }
188
+ if (onDis === "replace_me_with_judge") {
189
+ const judgeId = input.judge_manifest_id ?? "@agenteer/node-judge-with-stripped-ctx";
190
+ return {
191
+ kind: "replace_me",
192
+ reason: `cross_check_disagreement:${fingerprint}`,
193
+ successor: {
194
+ manifest_id: judgeId,
195
+ input: {
196
+ claim: "cross_check disagreement between primary and secondary outputs",
197
+ candidates: [primary, secondary],
198
+ disagreement_keys,
199
+ },
200
+ correlation: "cc-judge",
201
+ },
202
+ };
203
+ }
204
+ // return_both (default)
205
+ return {
206
+ kind: "output",
207
+ value: {
208
+ outcome:
209
+ outcome.kind === "missing_secondary_fallback_disagreement"
210
+ ? "missing_secondary_fallback_disagreement"
211
+ : "disagreement",
212
+ primary,
213
+ secondary,
214
+ disagreement_keys,
215
+ fingerprint,
216
+ },
217
+ evidence: { verdict: "fail", tool_output: { command: `cross_check:${input.schemaName}` } },
218
+ };
219
+ }
@@ -0,0 +1,171 @@
1
+ /**
2
+ * `@agenteer/node-judge-with-stripped-ctx` — meta-node that reaches a
3
+ * verdict on a claim **without the suspect node's reasoning trail**
4
+ * (sub-plan 03 §13).
5
+ *
6
+ * The judge spawns an LLM-backed sub-node with a slice that excludes
7
+ * `decision` items produced by the suspect (and their derivatives).
8
+ * Goal: detect motivated reasoning — a judge that still agrees when
9
+ * stripped of the suspect's narrative has genuine independent support.
10
+ *
11
+ * M4 implementation: the slice-filter layer is simple (exclude
12
+ * `source_node == suspect_node_id` + any `decision` items). A richer
13
+ * filter (suspect's `derives_from` closure) can land later.
14
+ */
15
+
16
+ import { z } from "zod";
17
+ import {
18
+ makeManifest,
19
+ type Node,
20
+ type NodeInput,
21
+ type NodeManifest,
22
+ type NodeResult,
23
+ } from "@agenteer/core";
24
+
25
+ const MANIFEST: NodeManifest = makeManifest({
26
+ id: "@agenteer/node-judge-with-stripped-ctx",
27
+ name: "judge_with_stripped_ctx",
28
+ description:
29
+ "Reach a verdict on a claim while stripping the suspect node's reasoning trail (decisions + derivatives).",
30
+ determinism: "stochastic",
31
+ required_actions: [],
32
+ dynamic_actions: true,
33
+ dynamic_action_spec: "spawn:${input.judge_manifest_id}",
34
+ tags: ["meta"],
35
+ side_effects: {
36
+ writes_fs: false,
37
+ network: true,
38
+ mutates_ctx: true,
39
+ emits_ctx_variants: ["artifact.judgment"],
40
+ },
41
+ });
42
+
43
+ const InputSchema = z.object({
44
+ /** What we're judging — free-text claim or a structured marker. */
45
+ claim: z.string().min(1),
46
+ /** Candidate answers / verdicts the judge picks between. */
47
+ candidates: z.array(z.unknown()).min(1),
48
+ /** Optional hint about which path differed (from cross_check). */
49
+ disagreement_keys: z.array(z.string()).optional(),
50
+ /** Manifest of the underlying judge node (usually an llm_call-shaped node). */
51
+ judge_manifest_id: z.string().default("@agenteer/node-llm-call"),
52
+ /** Model the judge should use. */
53
+ judge_model: z.string().optional(),
54
+ });
55
+
56
+ const OutputSchema = z.object({
57
+ verdict: z.enum(["candidate_0", "candidate_1", "neither", "cannot_judge"]),
58
+ chosen: z.unknown().optional(),
59
+ rationale: z.string(),
60
+ });
61
+
62
+ type Input = z.input<typeof InputSchema>;
63
+ type Output = z.infer<typeof OutputSchema>;
64
+
65
+ export function judgeWithStrippedCtxFactory(): Node<Input, Output> {
66
+ return {
67
+ manifest: MANIFEST,
68
+ inputSchema: InputSchema,
69
+ outputSchema: OutputSchema,
70
+ ctx: [],
71
+ model: null,
72
+ async execute(input: NodeInput<Input>): Promise<NodeResult<Output>> {
73
+ const i = input.original;
74
+
75
+ if (!input.children) {
76
+ const judgePrompt = buildJudgePrompt(i);
77
+ const judgeManifest = i.judge_manifest_id ?? "@agenteer/node-llm-call";
78
+ return {
79
+ kind: "spawn_children",
80
+ join: { mode: "all" },
81
+ children: [
82
+ {
83
+ manifest_id: judgeManifest,
84
+ input: {
85
+ model_id: i.judge_model ?? "mock/judge",
86
+ prompt: judgePrompt,
87
+ system:
88
+ "You are an impartial reviewer. Pick the candidate you believe is correct. If neither is supportable, answer `neither`. Return JSON.",
89
+ },
90
+ correlation: "judge",
91
+ },
92
+ ],
93
+ };
94
+ }
95
+
96
+ const child = input.children[0]!;
97
+ if (child.result.kind !== "output") {
98
+ return {
99
+ kind: "failed",
100
+ reason: `judge child did not produce output: ${child.result.kind}`,
101
+ retryable: false,
102
+ evidence: { verdict: "fail" },
103
+ };
104
+ }
105
+
106
+ const parsed = parseJudgeOutput((child.result as { value: unknown }).value, i);
107
+ return {
108
+ kind: "output",
109
+ value: parsed,
110
+ evidence: { verdict: parsed.verdict === "cannot_judge" ? "inconclusive" : "pass" },
111
+ };
112
+ },
113
+ };
114
+ }
115
+
116
+ export const judgeWithStrippedCtxManifest = MANIFEST;
117
+
118
+ function buildJudgePrompt(input: Input): string {
119
+ const candidates = input.candidates
120
+ .map((c, i) => `Candidate ${i}:\n${safeStringify(c)}`)
121
+ .join("\n\n");
122
+ const keys = input.disagreement_keys?.length
123
+ ? `\nDisagreement keys (paths that differ): ${input.disagreement_keys.join(", ")}\n`
124
+ : "";
125
+ return `Claim under review:\n${input.claim}\n${keys}\n${candidates}\n\nReturn JSON: { verdict: "candidate_0" | "candidate_1" | "neither" | "cannot_judge", rationale: string }`;
126
+ }
127
+
128
+ function parseJudgeOutput(raw: unknown, input: Input): Output {
129
+ // The child llm_call returns { value, model, tokens, method }. The
130
+ // schema-validated `value` is whatever the judge produced.
131
+ const inner = (raw as { value?: unknown }).value ?? raw;
132
+ let parsed: Record<string, unknown>;
133
+ if (typeof inner === "string") {
134
+ try {
135
+ parsed = JSON.parse(inner) as Record<string, unknown>;
136
+ } catch {
137
+ return {
138
+ verdict: "cannot_judge",
139
+ rationale: `judge returned unparseable text: ${inner.slice(0, 200)}`,
140
+ };
141
+ }
142
+ } else if (typeof inner === "object" && inner !== null) {
143
+ parsed = inner as Record<string, unknown>;
144
+ } else {
145
+ return { verdict: "cannot_judge", rationale: "judge returned no structured answer" };
146
+ }
147
+
148
+ const v = parsed["verdict"];
149
+ const rationale = typeof parsed["rationale"] === "string" ? (parsed["rationale"] as string) : "";
150
+
151
+ const verdict =
152
+ v === "candidate_0" || v === "candidate_1" || v === "neither" || v === "cannot_judge"
153
+ ? v
154
+ : "cannot_judge";
155
+
156
+ let chosen: unknown = undefined;
157
+ if (verdict === "candidate_0") chosen = input.candidates[0];
158
+ else if (verdict === "candidate_1") chosen = input.candidates[1];
159
+
160
+ const out: Output = { verdict, rationale };
161
+ if (chosen !== undefined) out.chosen = chosen;
162
+ return out;
163
+ }
164
+
165
+ function safeStringify(v: unknown): string {
166
+ try {
167
+ return JSON.stringify(v, null, 2);
168
+ } catch {
169
+ return String(v);
170
+ }
171
+ }
@@ -0,0 +1,142 @@
1
+ /**
2
+ * `@agenteer/node-parallel-fanout` — meta-node that spawns N children
3
+ * of a target manifest (sub-plan 03 §12), collects their results, and
4
+ * applies an optional merger. Children run in parallel up to the
5
+ * runtime's `max_concurrent_children`.
6
+ *
7
+ * Typical shape: run the same `llm_call` N times with different prompts,
8
+ * or run a validator against several candidate patches and take the
9
+ * best-verdict one.
10
+ */
11
+
12
+ import { z } from "zod";
13
+ import {
14
+ makeManifest,
15
+ type Node,
16
+ type NodeInput,
17
+ type NodeManifest,
18
+ type NodeResult,
19
+ } from "@agenteer/core";
20
+
21
+ const MANIFEST: NodeManifest = makeManifest({
22
+ id: "@agenteer/node-parallel-fanout",
23
+ name: "parallel_fanout",
24
+ description:
25
+ "Spawn N children of a target manifest with per-child inputs; collect and optionally merge results.",
26
+ determinism: "stochastic",
27
+ // Children handle their own capabilities; the fanout itself only
28
+ // spawns. Runtime narrows via the parent's envelope.
29
+ required_actions: [],
30
+ dynamic_actions: true,
31
+ dynamic_action_spec: "spawn:${input.manifest_id}",
32
+ tags: ["meta"],
33
+ });
34
+
35
+ const InputSchema = z.object({
36
+ manifest_id: z.string().min(1),
37
+ /** Per-child input payload. Length decides the fan-out degree. */
38
+ inputs: z.array(z.unknown()).min(1),
39
+ /** How to consume children. `all` re-enters with every result; `any`
40
+ * short-circuits on the first output; `race` bounds by a budget. */
41
+ join: z
42
+ .union([
43
+ z.object({ mode: z.literal("all") }),
44
+ z.object({ mode: z.literal("any") }),
45
+ z.object({
46
+ mode: z.literal("race_with_budget"),
47
+ budget_ms: z.number().int().positive(),
48
+ min_results: z.number().int().positive(),
49
+ }),
50
+ ])
51
+ .default({ mode: "all" }),
52
+ /** Optional merger — defaults to returning the raw array. */
53
+ merge: z
54
+ .union([
55
+ z.literal("concat"),
56
+ z.literal("first_pass"),
57
+ z.literal("first_output"),
58
+ ])
59
+ .default("concat"),
60
+ attenuate: z.array(z.string()).optional(),
61
+ });
62
+
63
+ const OutputSchema = z.object({
64
+ mode: z.enum(["all", "any", "race_with_budget"]),
65
+ /** Per-child summary: correlation + result-kind + (for output) value. */
66
+ children: z.array(
67
+ z.object({
68
+ correlation: z.string(),
69
+ kind: z.string(),
70
+ value: z.unknown().optional(),
71
+ reason: z.string().optional(),
72
+ }),
73
+ ),
74
+ /** Merged payload per `merge`. */
75
+ merged: z.unknown(),
76
+ });
77
+
78
+ type Input = z.input<typeof InputSchema>;
79
+ type Output = z.infer<typeof OutputSchema>;
80
+
81
+ export function parallelFanoutFactory(): Node<Input, Output> {
82
+ return {
83
+ manifest: MANIFEST,
84
+ inputSchema: InputSchema,
85
+ outputSchema: OutputSchema,
86
+ ctx: [],
87
+ model: null,
88
+ async execute(input: NodeInput<Input>): Promise<NodeResult<Output>> {
89
+ const spec = input.original;
90
+
91
+ if (!input.children) {
92
+ // First call: fan out.
93
+ return {
94
+ kind: "spawn_children",
95
+ join: spec.join ?? { mode: "all" },
96
+ children: spec.inputs.map((childInput, i) => ({
97
+ manifest_id: spec.manifest_id,
98
+ input: childInput,
99
+ correlation: `fan-${i}`,
100
+ ...(spec.attenuate ? { attenuate: spec.attenuate } : {}),
101
+ })),
102
+ };
103
+ }
104
+
105
+ // Re-entry: collect + merge.
106
+ const children = input.children.map((c) => {
107
+ const base = { correlation: c.correlation, kind: c.result.kind };
108
+ if (c.result.kind === "output") {
109
+ return { ...base, value: (c.result as { value: unknown }).value };
110
+ }
111
+ if (c.result.kind === "failed") {
112
+ return { ...base, reason: (c.result as { reason: string }).reason };
113
+ }
114
+ return base;
115
+ });
116
+
117
+ const mergeMode = spec.merge ?? "concat";
118
+ const values: unknown[] = [];
119
+ for (const c of children) {
120
+ if (c.kind === "output" && "value" in c) values.push((c as { value: unknown }).value);
121
+ }
122
+ const merged =
123
+ mergeMode === "first_output"
124
+ ? (values[0] ?? null)
125
+ : mergeMode === "first_pass"
126
+ ? (values.find((v) => !!v) ?? null)
127
+ : values;
128
+
129
+ const mode = (spec.join ?? { mode: "all" }).mode;
130
+ return {
131
+ kind: "output",
132
+ value: { mode, children, merged },
133
+ evidence: {
134
+ verdict:
135
+ children.some((c) => c.kind === "output") ? "pass" : "fail",
136
+ },
137
+ };
138
+ },
139
+ };
140
+ }
141
+
142
+ export const parallelFanoutManifest = MANIFEST;