@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.
- package/README.md +98 -0
- package/dist/.tsbuildinfo +1 -0
- package/dist/context/context_curator.d.ts +115 -0
- package/dist/context/context_curator.d.ts.map +1 -0
- package/dist/context/context_curator.js +132 -0
- package/dist/context/context_curator.js.map +1 -0
- package/dist/humans/approval_gate.d.ts +82 -0
- package/dist/humans/approval_gate.d.ts.map +1 -0
- package/dist/humans/approval_gate.js +76 -0
- package/dist/humans/approval_gate.js.map +1 -0
- package/dist/humans/ask_user.d.ts +79 -0
- package/dist/humans/ask_user.d.ts.map +1 -0
- package/dist/humans/ask_user.js +101 -0
- package/dist/humans/ask_user.js.map +1 -0
- package/dist/index.d.ts +49 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +93 -0
- package/dist/index.js.map +1 -0
- package/dist/meta/cross_check.d.ts +96 -0
- package/dist/meta/cross_check.d.ts.map +1 -0
- package/dist/meta/cross_check.js +165 -0
- package/dist/meta/cross_check.js.map +1 -0
- package/dist/meta/judge_with_stripped_ctx.d.ts +75 -0
- package/dist/meta/judge_with_stripped_ctx.d.ts.map +1 -0
- package/dist/meta/judge_with_stripped_ctx.js +151 -0
- package/dist/meta/judge_with_stripped_ctx.js.map +1 -0
- package/dist/meta/parallel_fanout.d.ts +83 -0
- package/dist/meta/parallel_fanout.d.ts.map +1 -0
- package/dist/meta/parallel_fanout.js +120 -0
- package/dist/meta/parallel_fanout.js.map +1 -0
- package/dist/meta/repair_loop.d.ts +90 -0
- package/dist/meta/repair_loop.d.ts.map +1 -0
- package/dist/meta/repair_loop.js +230 -0
- package/dist/meta/repair_loop.js.map +1 -0
- package/dist/planner/default_planner.d.ts +88 -0
- package/dist/planner/default_planner.d.ts.map +1 -0
- package/dist/planner/default_planner.js +156 -0
- package/dist/planner/default_planner.js.map +1 -0
- package/dist/primitives/file_read.d.ts +60 -0
- package/dist/primitives/file_read.d.ts.map +1 -0
- package/dist/primitives/file_read.js +68 -0
- package/dist/primitives/file_read.js.map +1 -0
- package/dist/primitives/file_write.d.ts +60 -0
- package/dist/primitives/file_write.d.ts.map +1 -0
- package/dist/primitives/file_write.js +66 -0
- package/dist/primitives/file_write.js.map +1 -0
- package/dist/primitives/llm_call.d.ts +85 -0
- package/dist/primitives/llm_call.d.ts.map +1 -0
- package/dist/primitives/llm_call.js +99 -0
- package/dist/primitives/llm_call.js.map +1 -0
- package/dist/primitives/shell_exec.d.ts +66 -0
- package/dist/primitives/shell_exec.d.ts.map +1 -0
- package/dist/primitives/shell_exec.js +106 -0
- package/dist/primitives/shell_exec.js.map +1 -0
- package/dist/primitives/tool_call.d.ts +62 -0
- package/dist/primitives/tool_call.d.ts.map +1 -0
- package/dist/primitives/tool_call.js +69 -0
- package/dist/primitives/tool_call.js.map +1 -0
- package/dist/shared/capture.d.ts +34 -0
- package/dist/shared/capture.d.ts.map +1 -0
- package/dist/shared/capture.js +124 -0
- package/dist/shared/capture.js.map +1 -0
- package/dist/shared/ctx.d.ts +14 -0
- package/dist/shared/ctx.d.ts.map +1 -0
- package/dist/shared/ctx.js +48 -0
- package/dist/shared/ctx.js.map +1 -0
- package/dist/shared/index.d.ts +4 -0
- package/dist/shared/index.d.ts.map +1 -0
- package/dist/shared/index.js +4 -0
- package/dist/shared/index.js.map +1 -0
- package/dist/shared/validator.d.ts +47 -0
- package/dist/shared/validator.d.ts.map +1 -0
- package/dist/shared/validator.js +41 -0
- package/dist/shared/validator.js.map +1 -0
- package/dist/validators/compile.d.ts +83 -0
- package/dist/validators/compile.d.ts.map +1 -0
- package/dist/validators/compile.js +126 -0
- package/dist/validators/compile.js.map +1 -0
- package/dist/validators/json_schema_validate.d.ts +72 -0
- package/dist/validators/json_schema_validate.d.ts.map +1 -0
- package/dist/validators/json_schema_validate.js +85 -0
- package/dist/validators/json_schema_validate.js.map +1 -0
- package/dist/validators/regex_check.d.ts +64 -0
- package/dist/validators/regex_check.d.ts.map +1 -0
- package/dist/validators/regex_check.js +85 -0
- package/dist/validators/regex_check.js.map +1 -0
- package/dist/validators/test_run.d.ts +74 -0
- package/dist/validators/test_run.d.ts.map +1 -0
- package/dist/validators/test_run.js +149 -0
- package/dist/validators/test_run.js.map +1 -0
- package/dist/validators/typecheck.d.ts +61 -0
- package/dist/validators/typecheck.d.ts.map +1 -0
- package/dist/validators/typecheck.js +89 -0
- package/dist/validators/typecheck.js.map +1 -0
- package/package.json +61 -0
- package/src/context/context_curator.ts +154 -0
- package/src/humans/approval_gate.ts +101 -0
- package/src/humans/ask_user.ts +137 -0
- package/src/index.ts +149 -0
- package/src/meta/cross_check.ts +219 -0
- package/src/meta/judge_with_stripped_ctx.ts +171 -0
- package/src/meta/parallel_fanout.ts +142 -0
- package/src/meta/repair_loop.ts +267 -0
- package/src/planner/default_planner.ts +182 -0
- package/src/primitives/file_read.ts +82 -0
- package/src/primitives/file_write.ts +80 -0
- package/src/primitives/llm_call.ts +113 -0
- package/src/primitives/shell_exec.ts +122 -0
- package/src/primitives/tool_call.ts +84 -0
- package/src/shared/capture.ts +155 -0
- package/src/shared/ctx.ts +45 -0
- package/src/shared/index.ts +17 -0
- package/src/shared/validator.ts +51 -0
- package/src/validators/compile.ts +175 -0
- package/src/validators/json_schema_validate.ts +103 -0
- package/src/validators/regex_check.ts +107 -0
- package/src/validators/test_run.ts +199 -0
- 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;
|