@burtson-labs/agent-core 1.6.43 → 1.6.45

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.
@@ -3,4 +3,6 @@ export * from './scheduler';
3
3
  export * from './loopNode';
4
4
  export * from './contracts';
5
5
  export * from './envelopes';
6
+ export * from './planner';
7
+ export * from './routing';
6
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/graph/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/graph/index.ts"],"names":[],"mappings":"AAAA,cAAc,SAAS,CAAC;AACxB,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC"}
@@ -19,4 +19,6 @@ __exportStar(require("./scheduler"), exports);
19
19
  __exportStar(require("./loopNode"), exports);
20
20
  __exportStar(require("./contracts"), exports);
21
21
  __exportStar(require("./envelopes"), exports);
22
+ __exportStar(require("./planner"), exports);
23
+ __exportStar(require("./routing"), exports);
22
24
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/graph/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,0CAAwB;AACxB,8CAA4B;AAC5B,6CAA2B;AAC3B,8CAA4B;AAC5B,8CAA4B"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/graph/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,0CAAwB;AACxB,8CAA4B;AAC5B,6CAA2B;AAC3B,8CAA4B;AAC5B,8CAA4B;AAC5B,4CAA0B;AAC1B,4CAA0B"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Phase 9 — the planner: let the MODEL propose a graph; the HOST owns
3
+ * everything that runs.
4
+ *
5
+ * Hard boundary, per the plan ("the planner never owns the scheduler"):
6
+ * a proposal is pure DATA — ids, dependencies, per-node prompts, and a
7
+ * read-only hint. The model cannot name tools, pick executors, set
8
+ * concurrency, or touch scheduling. The host validates the proposal
9
+ * (structure, caps, DAG-ness), maps hints to envelopes it controls, builds
10
+ * the executors itself, and runs the graph with ITS options. A malicious or
11
+ * confused proposal can therefore only produce a smaller/different DAG of
12
+ * host-controlled work — never wider capabilities.
13
+ *
14
+ * Classification contract: the model first decides HOW a task should run —
15
+ * direct one answer, no tools needed beyond a single turn
16
+ * loop one focused multi-step turn (today's tool loop)
17
+ * graph ≥2 separable chunks that benefit from explicit deps/parallelism
18
+ * "graph always wins" is exactly the assumption Phase 10's bench exists to
19
+ * test, so `direct`/`loop` are first-class outcomes, not failures.
20
+ */
21
+ import { type GraphSpec, type NodeEnvelope, type NodeExecutor } from './types';
22
+ export type ExecutionKind = 'direct' | 'loop' | 'graph';
23
+ /** One node as the model proposes it. Deliberately narrow: no tool names, no
24
+ * envelopes, no scheduler knobs — just work description + shape. */
25
+ export interface ProposalNode {
26
+ id: string;
27
+ /** Self-contained instruction for this node's turn. */
28
+ prompt: string;
29
+ label?: string;
30
+ dependsOn?: string[];
31
+ /** Hint that this node only reads/inspects. The HOST decides what envelope
32
+ * that maps to; in v1 hosts run ALL planned nodes read-only regardless. */
33
+ readOnly?: boolean;
34
+ }
35
+ export interface GraphProposal {
36
+ kind: ExecutionKind;
37
+ /** One sentence the UI can show for the classification. */
38
+ reason?: string;
39
+ /** Present iff kind === 'graph'. */
40
+ nodes?: ProposalNode[];
41
+ }
42
+ export interface PlannerLimits {
43
+ /** Cap on proposed nodes. Default 6 — enough to prove decomposition,
44
+ * small enough that a runaway proposal can't fan out a fleet. */
45
+ maxNodes?: number;
46
+ }
47
+ /** The classification+proposal prompt. One completion, no tools. */
48
+ export declare function buildPlannerPrompt(task: string, limits?: PlannerLimits): string;
49
+ export interface ParsedProposal {
50
+ ok: boolean;
51
+ proposal?: GraphProposal;
52
+ errors: string[];
53
+ }
54
+ /**
55
+ * Parse + strictly validate a model response into a proposal. Never throws.
56
+ * Accepts a ```json fence or bare JSON amid prose (first balanced object).
57
+ */
58
+ export declare function parseGraphProposal(text: string, limits?: PlannerLimits): ParsedProposal;
59
+ export interface MaterializeOptions {
60
+ /** Host-built executor for one proposed node (typically wrapLoopAsNode over
61
+ * the node's prompt). The proposal never supplies executors. */
62
+ makeExecutor: (node: ProposalNode) => NodeExecutor;
63
+ /** Host-owned envelope for one proposed node. The proposal's readOnly flag
64
+ * is a HINT; the host decides the actual bounds and may ignore or tighten
65
+ * it — it can never be loosened by the proposal. */
66
+ envelopeFor: (node: ProposalNode) => NodeEnvelope | undefined;
67
+ }
68
+ /** Turn a validated graph proposal into a runnable spec + executor map. */
69
+ export declare function materializeProposal(proposal: GraphProposal, opts: MaterializeOptions): {
70
+ spec: GraphSpec;
71
+ executors: Record<string, NodeExecutor>;
72
+ };
73
+ //# sourceMappingURL=planner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"planner.d.ts","sourceRoot":"","sources":["../../src/graph/planner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,EAAqC,KAAK,SAAS,EAAE,KAAK,YAAY,EAAE,KAAK,YAAY,EAAE,MAAM,SAAS,CAAC;AAElH,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,CAAC;AAExD;qEACqE;AACrE,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,uDAAuD;IACvD,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,EAAE,CAAC;IACrB;gFAC4E;IAC5E,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,aAAa,CAAC;IACpB,2DAA2D;IAC3D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oCAAoC;IACpC,KAAK,CAAC,EAAE,YAAY,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B;sEACkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAID,oEAAoE;AACpE,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,GAAE,aAAkB,GAAG,MAAM,CAuBnF;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,OAAO,CAAC;IACZ,QAAQ,CAAC,EAAE,aAAa,CAAC;IACzB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,GAAE,aAAkB,GAAG,cAAc,CA8D3F;AA6BD,MAAM,WAAW,kBAAkB;IACjC;qEACiE;IACjE,YAAY,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,YAAY,CAAC;IACnD;;yDAEqD;IACrD,WAAW,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK,YAAY,GAAG,SAAS,CAAC;CAC/D;AAED,2EAA2E;AAC3E,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,aAAa,EACvB,IAAI,EAAE,kBAAkB,GACvB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;CAAE,CAgB9D"}
@@ -0,0 +1,175 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildPlannerPrompt = buildPlannerPrompt;
4
+ exports.parseGraphProposal = parseGraphProposal;
5
+ exports.materializeProposal = materializeProposal;
6
+ /**
7
+ * Phase 9 — the planner: let the MODEL propose a graph; the HOST owns
8
+ * everything that runs.
9
+ *
10
+ * Hard boundary, per the plan ("the planner never owns the scheduler"):
11
+ * a proposal is pure DATA — ids, dependencies, per-node prompts, and a
12
+ * read-only hint. The model cannot name tools, pick executors, set
13
+ * concurrency, or touch scheduling. The host validates the proposal
14
+ * (structure, caps, DAG-ness), maps hints to envelopes it controls, builds
15
+ * the executors itself, and runs the graph with ITS options. A malicious or
16
+ * confused proposal can therefore only produce a smaller/different DAG of
17
+ * host-controlled work — never wider capabilities.
18
+ *
19
+ * Classification contract: the model first decides HOW a task should run —
20
+ * direct one answer, no tools needed beyond a single turn
21
+ * loop one focused multi-step turn (today's tool loop)
22
+ * graph ≥2 separable chunks that benefit from explicit deps/parallelism
23
+ * "graph always wins" is exactly the assumption Phase 10's bench exists to
24
+ * test, so `direct`/`loop` are first-class outcomes, not failures.
25
+ */
26
+ const types_1 = require("./types");
27
+ const DEFAULT_MAX_NODES = 6;
28
+ /** The classification+proposal prompt. One completion, no tools. */
29
+ function buildPlannerPrompt(task, limits = {}) {
30
+ const maxNodes = limits.maxNodes ?? DEFAULT_MAX_NODES;
31
+ return [
32
+ 'You are a planning classifier for a coding agent. Decide how the task below should run and answer with ONE JSON object in a ```json fence and nothing else.',
33
+ '',
34
+ 'Choose "kind":',
35
+ '- "direct": answerable in one response without multi-step tool work.',
36
+ '- "loop": one focused multi-step turn (read/edit/verify in sequence) — the default for most coding tasks.',
37
+ `- "graph": ONLY when the task splits into 2-${maxNodes} separable chunks where explicit dependencies or parallelism genuinely help (e.g. survey several areas independently, then synthesize).`,
38
+ '',
39
+ 'JSON shape:',
40
+ '{"kind":"direct"|"loop"|"graph","reason":"one sentence","nodes":[{"id":"kebab-case","label":"short","prompt":"self-contained instruction","dependsOn":["other-id"],"readOnly":true}]}',
41
+ '',
42
+ 'Rules for "graph":',
43
+ `- 2 to ${maxNodes} nodes; ids kebab-case and unique; dependsOn only lists earlier-declared ids; no cycles.`,
44
+ '- Each node prompt must stand alone (its reader sees ONLY that prompt plus its dependencies\' results).',
45
+ '- End with a node that depends on the others and synthesizes the final answer.',
46
+ '- Mark nodes that only read/inspect with "readOnly": true.',
47
+ '- Omit "nodes" entirely for "direct" and "loop".',
48
+ '',
49
+ 'Task:',
50
+ task.trim()
51
+ ].join('\n');
52
+ }
53
+ /**
54
+ * Parse + strictly validate a model response into a proposal. Never throws.
55
+ * Accepts a ```json fence or bare JSON amid prose (first balanced object).
56
+ */
57
+ function parseGraphProposal(text, limits = {}) {
58
+ const maxNodes = limits.maxNodes ?? DEFAULT_MAX_NODES;
59
+ const raw = extractJsonObject(text);
60
+ if (!raw)
61
+ return { ok: false, errors: ['no JSON object found in the planner response'] };
62
+ let parsed;
63
+ try {
64
+ parsed = JSON.parse(raw);
65
+ }
66
+ catch {
67
+ return { ok: false, errors: ['planner response JSON does not parse'] };
68
+ }
69
+ const obj = parsed;
70
+ const errors = [];
71
+ if (obj.kind !== 'direct' && obj.kind !== 'loop' && obj.kind !== 'graph') {
72
+ errors.push(`kind must be direct|loop|graph (got ${JSON.stringify(obj.kind)})`);
73
+ }
74
+ let nodes;
75
+ if (obj.kind === 'graph') {
76
+ if (!Array.isArray(obj.nodes) || obj.nodes.length === 0) {
77
+ errors.push('graph proposals need a non-empty "nodes" array');
78
+ }
79
+ else if (obj.nodes.length > maxNodes) {
80
+ errors.push(`too many nodes: ${obj.nodes.length} (max ${maxNodes})`);
81
+ }
82
+ else {
83
+ nodes = [];
84
+ for (const [i, n] of obj.nodes.entries()) {
85
+ const node = n;
86
+ if (!node || typeof node.id !== 'string' || node.id.trim() === '') {
87
+ errors.push(`node[${i}]: missing id`);
88
+ continue;
89
+ }
90
+ if (typeof node.prompt !== 'string' || node.prompt.trim().length < 8) {
91
+ errors.push(`node "${node.id}": missing or trivial prompt`);
92
+ continue;
93
+ }
94
+ nodes.push({
95
+ id: node.id.trim(),
96
+ prompt: node.prompt.trim(),
97
+ label: typeof node.label === 'string' ? node.label : undefined,
98
+ dependsOn: Array.isArray(node.dependsOn) ? node.dependsOn.filter((d) => typeof d === 'string') : undefined,
99
+ readOnly: node.readOnly === true,
100
+ });
101
+ }
102
+ if (errors.length === 0 && nodes.length > 0) {
103
+ // Structural validation via the same code the scheduler trusts.
104
+ const structural = (0, types_1.validateGraph)({ nodes: nodes.map(({ id, dependsOn }) => ({ id, dependsOn })) });
105
+ if (!structural.ok)
106
+ errors.push(...structural.errors);
107
+ }
108
+ }
109
+ }
110
+ if (errors.length > 0)
111
+ return { ok: false, errors };
112
+ return {
113
+ ok: true,
114
+ errors: [],
115
+ proposal: {
116
+ kind: obj.kind,
117
+ reason: typeof obj.reason === 'string' ? obj.reason : undefined,
118
+ nodes,
119
+ },
120
+ };
121
+ }
122
+ /** First ```json fence, else the first balanced top-level {...}. */
123
+ function extractJsonObject(text) {
124
+ const fence = /```(?:json)?\s*([\s\S]*?)```/.exec(text);
125
+ if (fence && fence[1].trim().startsWith('{'))
126
+ return fence[1].trim();
127
+ const start = text.indexOf('{');
128
+ if (start === -1)
129
+ return null;
130
+ let depth = 0;
131
+ let inString = false;
132
+ let escaped = false;
133
+ for (let i = start; i < text.length; i++) {
134
+ const ch = text[i];
135
+ if (inString) {
136
+ if (escaped)
137
+ escaped = false;
138
+ else if (ch === '\\')
139
+ escaped = true;
140
+ else if (ch === '"')
141
+ inString = false;
142
+ continue;
143
+ }
144
+ if (ch === '"')
145
+ inString = true;
146
+ else if (ch === '{')
147
+ depth += 1;
148
+ else if (ch === '}') {
149
+ depth -= 1;
150
+ if (depth === 0)
151
+ return text.slice(start, i + 1);
152
+ }
153
+ }
154
+ return null;
155
+ }
156
+ /** Turn a validated graph proposal into a runnable spec + executor map. */
157
+ function materializeProposal(proposal, opts) {
158
+ if (proposal.kind !== 'graph' || !proposal.nodes || proposal.nodes.length === 0) {
159
+ throw new Error('materializeProposal: only graph proposals with nodes can be materialized');
160
+ }
161
+ const specNodes = proposal.nodes.map((n) => ({
162
+ id: n.id,
163
+ label: n.label ?? n.id,
164
+ dependsOn: n.dependsOn,
165
+ envelope: opts.envelopeFor(n),
166
+ // Every planned node must actually produce something — silence is the
167
+ // graph version of a claim without work.
168
+ contract: { outputNonEmpty: true },
169
+ }));
170
+ const executors = {};
171
+ for (const n of proposal.nodes)
172
+ executors[n.id] = opts.makeExecutor(n);
173
+ return { spec: { nodes: specNodes }, executors };
174
+ }
175
+ //# sourceMappingURL=planner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"planner.js","sourceRoot":"","sources":["../../src/graph/planner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Phase 10 — routing heuristics (the cheap pre-gate).
3
+ *
4
+ * Empirical routing (Phase 10 proper) needs the BanditBench baseline to say
5
+ * WHICH execution mode wins for WHICH task shape. But calling the planner
6
+ * model on every prompt just to hear "this is a plain loop" is wasteful — most
7
+ * prompts are obviously not graph-shaped. This module is the zero-inference
8
+ * pre-gate: a fast, explainable signal for whether a prompt is even worth
9
+ * asking the planner about.
10
+ *
11
+ * It NEVER decides execution — it only gates the (paid) planner call:
12
+ * score low → don't bother the planner; run the normal loop
13
+ * score high → the prompt smells decomposable; offer/consult the planner
14
+ *
15
+ * Deliberately conservative and transparent (returns its reasons) so a host
16
+ * can log why it routed, and so tuning it against the bench is inspectable
17
+ * rather than a black box. "graph rarely wins" is the null hypothesis the
18
+ * bench exists to test, so the bar to even SUGGEST a graph is high.
19
+ */
20
+ export interface GraphShapeSignal {
21
+ /** 0..1 — how graph-shaped the prompt looks. */
22
+ score: number;
23
+ /** Human-readable contributors, for logging + tuning. */
24
+ reasons: string[];
25
+ /** Convenience: score >= the suggest threshold. */
26
+ suggestsGraph: boolean;
27
+ }
28
+ export interface GraphShapeOptions {
29
+ /** Score at/above which we'd consult the planner. Default 0.6 (high bar). */
30
+ suggestThreshold?: number;
31
+ }
32
+ /**
33
+ * Score how graph-shaped a prompt looks. Pure, fast, no model call.
34
+ */
35
+ export declare function classifyGraphShaped(prompt: string, opts?: GraphShapeOptions): GraphShapeSignal;
36
+ //# sourceMappingURL=routing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routing.d.ts","sourceRoot":"","sources":["../../src/graph/routing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,WAAW,gBAAgB;IAC/B,gDAAgD;IAChD,KAAK,EAAE,MAAM,CAAC;IACd,yDAAyD;IACzD,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,mDAAmD;IACnD,aAAa,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,iBAAiB;IAChC,6EAA6E;IAC7E,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AA+CD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,GAAE,iBAAsB,GAAG,gBAAgB,CA0DlG"}
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ /**
3
+ * Phase 10 — routing heuristics (the cheap pre-gate).
4
+ *
5
+ * Empirical routing (Phase 10 proper) needs the BanditBench baseline to say
6
+ * WHICH execution mode wins for WHICH task shape. But calling the planner
7
+ * model on every prompt just to hear "this is a plain loop" is wasteful — most
8
+ * prompts are obviously not graph-shaped. This module is the zero-inference
9
+ * pre-gate: a fast, explainable signal for whether a prompt is even worth
10
+ * asking the planner about.
11
+ *
12
+ * It NEVER decides execution — it only gates the (paid) planner call:
13
+ * score low → don't bother the planner; run the normal loop
14
+ * score high → the prompt smells decomposable; offer/consult the planner
15
+ *
16
+ * Deliberately conservative and transparent (returns its reasons) so a host
17
+ * can log why it routed, and so tuning it against the bench is inspectable
18
+ * rather than a black box. "graph rarely wins" is the null hypothesis the
19
+ * bench exists to test, so the bar to even SUGGEST a graph is high.
20
+ */
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.classifyGraphShaped = classifyGraphShaped;
23
+ // Phrases that signal INDEPENDENT sub-parts (the thing a graph is for).
24
+ const MULTI_PART_MARKERS = [
25
+ /\bcompare\b[\s\S]*\b(?:and|vs\.?|versus|with|to|against)\b/i, // two-sided compare
26
+ /\bcontrast\b/i,
27
+ /\beach of\b/i,
28
+ /\bboth\b[\s\S]*\band\b/i,
29
+ /\bin parallel\b/i,
30
+ /\bseparately\b/i,
31
+ /\bindependently\b/i,
32
+ /\bacross (the |these |all )?\w+/i,
33
+ /\bfor (?:each|every)\b/i,
34
+ // Sequential fan-in: "…then/finally summarize/combine/merge the results".
35
+ /\b(?:then|afterwards|finally)\b[\s\S]*\b(?:summari[sz]|synthesi[sz]|combin|merg|compil)/i,
36
+ ];
37
+ // Explicit enumeration: "A, B, and C" or "A and B" of comparable nouns.
38
+ const AND_LIST = /\b\w[\w./-]*(?:,\s*\w[\w./-]*)+\s*,?\s*and\s+\w[\w./-]*/i;
39
+ const SIMPLE_AND = /\band\b/i;
40
+ // A synthesis verb paired with multiple sources is the classic fan-in shape.
41
+ // "compar(e|ison)" counts here too — a comparison IS a synthesis over sources.
42
+ const SYNTHESIS_MARKERS = [
43
+ /\bsummari[sz]e\b/i,
44
+ /\bsynthesi[sz]e\b/i,
45
+ /\bcombine\b/i,
46
+ /\breport\b/i,
47
+ /\boverview\b/i,
48
+ /\bcompar(?:e|ison|ing)\b/i,
49
+ ];
50
+ // Strong single-step signals that argue AGAINST a graph regardless.
51
+ const SINGLE_STEP_MARKERS = [
52
+ /\bfix (the |a )?typo\b/i,
53
+ /\brename\b/i,
54
+ /\bbump (the )?version\b/i,
55
+ /^\s*(what|where|when|who|why|how)\b.*\?\s*$/i, // a single question
56
+ /\badd (a |an )?(comment|line|import|field)\b/i,
57
+ ];
58
+ /** Count distinct file-ish tokens (paths, dotted names) mentioned. */
59
+ function fileMentions(text) {
60
+ const matches = text.match(/\b[\w-]+\/[\w./-]+|\b[\w-]+\.(ts|tsx|js|jsx|py|cs|go|rs|md|json|ya?ml|sh)\b/gi) ?? [];
61
+ return new Set(matches.map((m) => m.toLowerCase())).size;
62
+ }
63
+ /**
64
+ * Score how graph-shaped a prompt looks. Pure, fast, no model call.
65
+ */
66
+ function classifyGraphShaped(prompt, opts = {}) {
67
+ const threshold = opts.suggestThreshold ?? 0.6;
68
+ const text = prompt.trim();
69
+ const reasons = [];
70
+ let score = 0;
71
+ // Very short prompts are almost never worth decomposing.
72
+ const words = text.split(/\s+/).filter(Boolean).length;
73
+ if (words < 8) {
74
+ return { score: 0, reasons: ['prompt too short to decompose'], suggestsGraph: false };
75
+ }
76
+ // Hard single-step signals cap the score low — a typo fix is a typo fix.
77
+ for (const re of SINGLE_STEP_MARKERS) {
78
+ if (re.test(text)) {
79
+ return { score: 0.1, reasons: [`single-step marker: ${re.source}`], suggestsGraph: false };
80
+ }
81
+ }
82
+ const multiPartHits = MULTI_PART_MARKERS.filter((re) => re.test(text));
83
+ if (multiPartHits.length > 0) {
84
+ score += 0.4;
85
+ reasons.push(`multi-part phrasing (${multiPartHits.length})`);
86
+ }
87
+ const files = fileMentions(text);
88
+ if (files >= 3) {
89
+ score += 0.3;
90
+ reasons.push(`${files} distinct files/paths mentioned`);
91
+ }
92
+ else if (files === 2) {
93
+ score += 0.15;
94
+ reasons.push('2 files/paths mentioned');
95
+ }
96
+ const hasSynthesis = SYNTHESIS_MARKERS.some((re) => re.test(text));
97
+ const hasList = AND_LIST.test(text);
98
+ if (hasSynthesis && (hasList || multiPartHits.length > 0 || files >= 2)) {
99
+ // Synthesis over multiple things = the fan-in a graph handles well.
100
+ score += 0.3;
101
+ reasons.push('synthesis over multiple sources');
102
+ }
103
+ else if (hasList) {
104
+ score += 0.15;
105
+ reasons.push('enumerated list of targets');
106
+ }
107
+ else if (SIMPLE_AND.test(text) && words >= 20) {
108
+ // A bare "and" in a long prompt is weak evidence at most.
109
+ score += 0.05;
110
+ reasons.push('conjunction in a long prompt');
111
+ }
112
+ // Long, dense prompts have more room for separable work.
113
+ if (words >= 40) {
114
+ score += 0.1;
115
+ reasons.push('long prompt');
116
+ }
117
+ score = Math.min(1, score);
118
+ if (reasons.length === 0)
119
+ reasons.push('no graph-shape signals');
120
+ return { score, reasons, suggestsGraph: score >= threshold };
121
+ }
122
+ //# sourceMappingURL=routing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routing.js","sourceRoot":"","sources":["../../src/graph/routing.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burtson-labs/agent-core",
3
- "version": "1.6.43",
3
+ "version": "1.6.45",
4
4
  "author": {
5
5
  "name": "Burtson Labs",
6
6
  "email": "team@burtson.ai",