@inf-minds/coordination-modes 0.0.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.
@@ -0,0 +1,4 @@
1
+ export type { MindRef, GraphNode, GraphEdge, ExecutionGraph, ArtifactMapping, RoutingRules, CoordinationMode, ValidationResult, } from './types.js';
2
+ export { validateGraph, isAcyclic, getTopologicalOrder, } from './validation.js';
3
+ export { createPipelineMode, type PipelineOptions, type PipelineStep, createManagerWorkersMode, type ManagerWorkersOptions, } from './modes/index.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,YAAY,EACV,OAAO,EACP,SAAS,EACT,SAAS,EACT,cAAc,EACd,eAAe,EACf,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,GACjB,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,aAAa,EACb,SAAS,EACT,mBAAmB,GACpB,MAAM,iBAAiB,CAAC;AAGzB,OAAO,EACL,kBAAkB,EAClB,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,wBAAwB,EACxB,KAAK,qBAAqB,GAC3B,MAAM,kBAAkB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ // ABOUTME: Coordination modes package entry point
2
+ // ABOUTME: Exports types, validation, and built-in modes
3
+ // Validation exports
4
+ export { validateGraph, isAcyclic, getTopologicalOrder, } from './validation.js';
5
+ // Mode exports
6
+ export { createPipelineMode, createManagerWorkersMode, } from './modes/index.js';
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,kDAAkD;AAClD,yDAAyD;AAczD,qBAAqB;AACrB,OAAO,EACL,aAAa,EACb,SAAS,EACT,mBAAmB,GACpB,MAAM,iBAAiB,CAAC;AAEzB,eAAe;AACf,OAAO,EACL,kBAAkB,EAGlB,wBAAwB,GAEzB,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,3 @@
1
+ export { createPipelineMode, type PipelineOptions, type PipelineStep } from './pipeline.js';
2
+ export { createManagerWorkersMode, type ManagerWorkersOptions, } from './manager-workers.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/modes/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,kBAAkB,EAAE,KAAK,eAAe,EAAE,KAAK,YAAY,EAAE,MAAM,eAAe,CAAC;AAC5F,OAAO,EACL,wBAAwB,EACxB,KAAK,qBAAqB,GAC3B,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,5 @@
1
+ // ABOUTME: Built-in coordination modes
2
+ // ABOUTME: Exports pipeline and manager-workers mode factories
3
+ export { createPipelineMode } from './pipeline.js';
4
+ export { createManagerWorkersMode, } from './manager-workers.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/modes/index.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,+DAA+D;AAE/D,OAAO,EAAE,kBAAkB,EAA2C,MAAM,eAAe,CAAC;AAC5F,OAAO,EACL,wBAAwB,GAEzB,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,48 @@
1
+ import type { CoordinationMode, MindRef } from '../types.js';
2
+ /**
3
+ * Options for creating a manager-workers mode.
4
+ */
5
+ export interface ManagerWorkersOptions {
6
+ /** Name of the mode */
7
+ name?: string;
8
+ /** Description of the mode */
9
+ description?: string;
10
+ /** Mind for the manager role */
11
+ managerMind: MindRef;
12
+ /** Mind for the worker role */
13
+ workerMind: MindRef;
14
+ /** Maximum iterations for the manager loop (default: 10) */
15
+ maxIterations?: number;
16
+ /**
17
+ * Exit condition for the manager.
18
+ * Evaluated against manager's output.
19
+ * Default: 'output.allTasksComplete'
20
+ */
21
+ exitCondition?: string;
22
+ /**
23
+ * Delegation condition.
24
+ * If true, manager delegates to worker.
25
+ * Default: 'output.delegatedTask'
26
+ */
27
+ delegationCondition?: string;
28
+ }
29
+ /**
30
+ * Create a manager-workers coordination mode.
31
+ *
32
+ * The manager orchestrates work by delegating tasks to workers.
33
+ * After each worker completes, control returns to the manager
34
+ * who decides whether to delegate more work or complete.
35
+ *
36
+ * ```
37
+ * +--> worker --+
38
+ * | |
39
+ * --> manager <------+
40
+ * |
41
+ * +--> [exit when complete]
42
+ * ```
43
+ *
44
+ * @param options - Manager-workers configuration
45
+ * @returns A CoordinationMode for manager-workers pattern
46
+ */
47
+ export declare function createManagerWorkersMode(options: ManagerWorkersOptions): CoordinationMode;
48
+ //# sourceMappingURL=manager-workers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manager-workers.d.ts","sourceRoot":"","sources":["../../src/modes/manager-workers.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAE7D;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,uBAAuB;IACvB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,8BAA8B;IAC9B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gCAAgC;IAChC,WAAW,EAAE,OAAO,CAAC;IACrB,+BAA+B;IAC/B,UAAU,EAAE,OAAO,CAAC;IACpB,4DAA4D;IAC5D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,qBAAqB,GAAG,gBAAgB,CA8CzF"}
@@ -0,0 +1,59 @@
1
+ // ABOUTME: Manager-workers coordination mode
2
+ // ABOUTME: Central manager delegates tasks to workers in a loop
3
+ /**
4
+ * Create a manager-workers coordination mode.
5
+ *
6
+ * The manager orchestrates work by delegating tasks to workers.
7
+ * After each worker completes, control returns to the manager
8
+ * who decides whether to delegate more work or complete.
9
+ *
10
+ * ```
11
+ * +--> worker --+
12
+ * | |
13
+ * --> manager <------+
14
+ * |
15
+ * +--> [exit when complete]
16
+ * ```
17
+ *
18
+ * @param options - Manager-workers configuration
19
+ * @returns A CoordinationMode for manager-workers pattern
20
+ */
21
+ export function createManagerWorkersMode(options) {
22
+ const { name = 'manager-workers', description, managerMind, workerMind, maxIterations = 10, exitCondition = 'output.allTasksComplete', delegationCondition = 'output.delegatedTask', } = options;
23
+ return {
24
+ name,
25
+ description,
26
+ graph: {
27
+ nodes: {
28
+ manager: {
29
+ id: 'manager',
30
+ mind: managerMind,
31
+ maxIterations,
32
+ exitCondition,
33
+ },
34
+ worker: {
35
+ id: 'worker',
36
+ mind: workerMind,
37
+ },
38
+ },
39
+ edges: [
40
+ {
41
+ from: 'manager',
42
+ to: 'worker',
43
+ condition: delegationCondition,
44
+ },
45
+ {
46
+ from: 'worker',
47
+ to: 'manager',
48
+ },
49
+ ],
50
+ entryNode: 'manager',
51
+ exitNodes: ['manager'],
52
+ },
53
+ defaultRouting: {
54
+ defaultVisibility: 'session',
55
+ autoPropagateArtifacts: true,
56
+ },
57
+ };
58
+ }
59
+ //# sourceMappingURL=manager-workers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manager-workers.js","sourceRoot":"","sources":["../../src/modes/manager-workers.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,gEAAgE;AAgChE;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,UAAU,wBAAwB,CAAC,OAA8B;IACrE,MAAM,EACJ,IAAI,GAAG,iBAAiB,EACxB,WAAW,EACX,WAAW,EACX,UAAU,EACV,aAAa,GAAG,EAAE,EAClB,aAAa,GAAG,yBAAyB,EACzC,mBAAmB,GAAG,sBAAsB,GAC7C,GAAG,OAAO,CAAC;IAEZ,OAAO;QACL,IAAI;QACJ,WAAW;QACX,KAAK,EAAE;YACL,KAAK,EAAE;gBACL,OAAO,EAAE;oBACP,EAAE,EAAE,SAAS;oBACb,IAAI,EAAE,WAAW;oBACjB,aAAa;oBACb,aAAa;iBACd;gBACD,MAAM,EAAE;oBACN,EAAE,EAAE,QAAQ;oBACZ,IAAI,EAAE,UAAU;iBACjB;aACF;YACD,KAAK,EAAE;gBACL;oBACE,IAAI,EAAE,SAAS;oBACf,EAAE,EAAE,QAAQ;oBACZ,SAAS,EAAE,mBAAmB;iBAC/B;gBACD;oBACE,IAAI,EAAE,QAAQ;oBACd,EAAE,EAAE,SAAS;iBACd;aACF;YACD,SAAS,EAAE,SAAS;YACpB,SAAS,EAAE,CAAC,SAAS,CAAC;SACvB;QACD,cAAc,EAAE;YACd,iBAAiB,EAAE,SAAS;YAC5B,sBAAsB,EAAE,IAAI;SAC7B;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,36 @@
1
+ import type { CoordinationMode, MindRef } from '../types.js';
2
+ /**
3
+ * A step in the pipeline.
4
+ */
5
+ export interface PipelineStep {
6
+ /** Unique identifier for this step */
7
+ id: string;
8
+ /** Mind to execute at this step */
9
+ mind: MindRef;
10
+ }
11
+ /**
12
+ * Options for creating a pipeline mode.
13
+ */
14
+ export interface PipelineOptions {
15
+ /** Name of the pipeline */
16
+ name?: string;
17
+ /** Description of the pipeline */
18
+ description?: string;
19
+ /** Steps in the pipeline */
20
+ steps: PipelineStep[];
21
+ }
22
+ /**
23
+ * Create a pipeline coordination mode.
24
+ *
25
+ * A pipeline executes minds sequentially, passing artifacts
26
+ * from one step to the next.
27
+ *
28
+ * ```
29
+ * step1 -> step2 -> step3 -> ... -> stepN
30
+ * ```
31
+ *
32
+ * @param options - Pipeline configuration
33
+ * @returns A CoordinationMode for sequential execution
34
+ */
35
+ export declare function createPipelineMode(options: PipelineOptions): CoordinationMode;
36
+ //# sourceMappingURL=pipeline.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../src/modes/pipeline.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,OAAO,EAAwB,MAAM,aAAa,CAAC;AAEnF;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,sCAAsC;IACtC,EAAE,EAAE,MAAM,CAAC;IACX,mCAAmC;IACnC,IAAI,EAAE,OAAO,CAAC;CACf;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,2BAA2B;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,kCAAkC;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4BAA4B;IAC5B,KAAK,EAAE,YAAY,EAAE,CAAC;CACvB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,eAAe,GAAG,gBAAgB,CA6C7E"}
@@ -0,0 +1,57 @@
1
+ // ABOUTME: Pipeline coordination mode
2
+ // ABOUTME: Sequential execution of minds in a linear chain
3
+ /**
4
+ * Create a pipeline coordination mode.
5
+ *
6
+ * A pipeline executes minds sequentially, passing artifacts
7
+ * from one step to the next.
8
+ *
9
+ * ```
10
+ * step1 -> step2 -> step3 -> ... -> stepN
11
+ * ```
12
+ *
13
+ * @param options - Pipeline configuration
14
+ * @returns A CoordinationMode for sequential execution
15
+ */
16
+ export function createPipelineMode(options) {
17
+ const { steps, name = 'pipeline', description } = options;
18
+ if (steps.length === 0) {
19
+ throw new Error('Pipeline must have at least one step');
20
+ }
21
+ // Build nodes
22
+ const nodes = {};
23
+ for (const step of steps) {
24
+ nodes[step.id] = {
25
+ id: step.id,
26
+ mind: step.mind,
27
+ };
28
+ }
29
+ // Build edges (connect each step to the next)
30
+ const edges = [];
31
+ for (let i = 0; i < steps.length - 1; i++) {
32
+ const current = steps[i];
33
+ const next = steps[i + 1];
34
+ edges.push({
35
+ from: current.id,
36
+ to: next.id,
37
+ });
38
+ }
39
+ // Safe to assert since we checked steps.length > 0
40
+ const firstStep = steps[0];
41
+ const lastStep = steps[steps.length - 1];
42
+ return {
43
+ name,
44
+ description,
45
+ graph: {
46
+ nodes,
47
+ edges,
48
+ entryNode: firstStep.id,
49
+ exitNodes: [lastStep.id],
50
+ },
51
+ defaultRouting: {
52
+ defaultVisibility: 'session',
53
+ autoPropagateArtifacts: true,
54
+ },
55
+ };
56
+ }
57
+ //# sourceMappingURL=pipeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline.js","sourceRoot":"","sources":["../../src/modes/pipeline.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,2DAA2D;AA0B3D;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAAwB;IACzD,MAAM,EAAE,KAAK,EAAE,IAAI,GAAG,UAAU,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC;IAE1D,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;IAC1D,CAAC;IAED,cAAc;IACd,MAAM,KAAK,GAA8B,EAAE,CAAC;IAC5C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,GAAG;YACf,EAAE,EAAE,IAAI,CAAC,EAAE;YACX,IAAI,EAAE,IAAI,CAAC,IAAI;SAChB,CAAC;IACJ,CAAC;IAED,8CAA8C;IAC9C,MAAM,KAAK,GAAgB,EAAE,CAAC;IAC9B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC;QAC1C,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC;QAC1B,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,CAAE,CAAC;QAC3B,KAAK,CAAC,IAAI,CAAC;YACT,IAAI,EAAE,OAAO,CAAC,EAAE;YAChB,EAAE,EAAE,IAAI,CAAC,EAAE;SACZ,CAAC,CAAC;IACL,CAAC;IAED,mDAAmD;IACnD,MAAM,SAAS,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC;IAC5B,MAAM,QAAQ,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAE,CAAC;IAE1C,OAAO;QACL,IAAI;QACJ,WAAW;QACX,KAAK,EAAE;YACL,KAAK;YACL,KAAK;YACL,SAAS,EAAE,SAAS,CAAC,EAAE;YACvB,SAAS,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC;SACzB;QACD,cAAc,EAAE;YACd,iBAAiB,EAAE,SAAS;YAC5B,sBAAsB,EAAE,IAAI;SAC7B;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,108 @@
1
+ import type { MindConfig } from '@inf-minds/mindkit';
2
+ /**
3
+ * Reference to a mind configuration.
4
+ * Can be inline (with full config) or registered (by name).
5
+ */
6
+ export interface MindRef {
7
+ /** Type of reference */
8
+ type: 'inline' | 'registered';
9
+ /** Full mind configuration (for inline type) */
10
+ config?: MindConfig;
11
+ /** Name of registered mind type (for registered type) */
12
+ mindType?: string;
13
+ }
14
+ /**
15
+ * A node in the execution graph.
16
+ */
17
+ export interface GraphNode {
18
+ /** Unique identifier for this node */
19
+ id: string;
20
+ /** Mind to execute at this node */
21
+ mind: MindRef;
22
+ /** Maximum iterations for loops (default: 1) */
23
+ maxIterations?: number;
24
+ /**
25
+ * Exit condition expression.
26
+ * Evaluated against the node's output.
27
+ * If true, the node exits normally.
28
+ * If false and maxIterations not reached, the node re-executes.
29
+ */
30
+ exitCondition?: string;
31
+ }
32
+ /**
33
+ * Artifact mapping for edge transitions.
34
+ */
35
+ export interface ArtifactMapping {
36
+ /** Path patterns to include */
37
+ include?: string[];
38
+ /** Path patterns to exclude */
39
+ exclude?: string[];
40
+ /** Rename mappings (from -> to) */
41
+ rename?: Record<string, string>;
42
+ }
43
+ /**
44
+ * An edge connecting two nodes in the execution graph.
45
+ */
46
+ export interface GraphEdge {
47
+ /** Source node ID */
48
+ from: string;
49
+ /** Target node ID */
50
+ to: string;
51
+ /**
52
+ * Condition expression for conditional routing.
53
+ * Evaluated against the source node's output.
54
+ * If true (or no condition), the edge is traversed.
55
+ */
56
+ condition?: string;
57
+ /** Artifact mapping for this edge */
58
+ artifactMapping?: ArtifactMapping;
59
+ /** Named channel for artifact routing */
60
+ channel?: string;
61
+ }
62
+ /**
63
+ * The complete execution graph.
64
+ */
65
+ export interface ExecutionGraph {
66
+ /** All nodes in the graph */
67
+ nodes: Record<string, GraphNode>;
68
+ /** All edges connecting nodes */
69
+ edges: GraphEdge[];
70
+ /** ID of the entry node */
71
+ entryNode: string;
72
+ /** IDs of exit nodes (graph completes when one is reached) */
73
+ exitNodes: string[];
74
+ }
75
+ /**
76
+ * Default routing rules for artifacts.
77
+ */
78
+ export interface RoutingRules {
79
+ /** Default visibility for artifacts */
80
+ defaultVisibility?: 'private' | 'session' | 'persistent';
81
+ /** Auto-propagate artifacts along edges */
82
+ autoPropagateArtifacts?: boolean;
83
+ }
84
+ /**
85
+ * A coordination mode defines how multiple minds work together.
86
+ */
87
+ export interface CoordinationMode {
88
+ /** Mode name */
89
+ name: string;
90
+ /** Mode description */
91
+ description?: string;
92
+ /** The execution graph */
93
+ graph: ExecutionGraph;
94
+ /** Default routing rules */
95
+ defaultRouting?: RoutingRules;
96
+ }
97
+ /**
98
+ * Result of graph validation.
99
+ */
100
+ export interface ValidationResult {
101
+ /** Whether the graph is valid */
102
+ valid: boolean;
103
+ /** Validation errors */
104
+ errors: string[];
105
+ /** Validation warnings */
106
+ warnings: string[];
107
+ }
108
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAErD;;;GAGG;AACH,MAAM,WAAW,OAAO;IACtB,wBAAwB;IACxB,IAAI,EAAE,QAAQ,GAAG,YAAY,CAAC;IAC9B,gDAAgD;IAChD,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,yDAAyD;IACzD,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,sCAAsC;IACtC,EAAE,EAAE,MAAM,CAAC;IACX,mCAAmC;IACnC,IAAI,EAAE,OAAO,CAAC;IACd,gDAAgD;IAChD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,+BAA+B;IAC/B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,+BAA+B;IAC/B,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,mCAAmC;IACnC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACjC;AAED;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,qBAAqB;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,qBAAqB;IACrB,EAAE,EAAE,MAAM,CAAC;IACX;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,qCAAqC;IACrC,eAAe,CAAC,EAAE,eAAe,CAAC;IAClC,yCAAyC;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,6BAA6B;IAC7B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;IACjC,iCAAiC;IACjC,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,2BAA2B;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,8DAA8D;IAC9D,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,uCAAuC;IACvC,iBAAiB,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,YAAY,CAAC;IACzD,2CAA2C;IAC3C,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAClC;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,gBAAgB;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,uBAAuB;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0BAA0B;IAC1B,KAAK,EAAE,cAAc,CAAC;IACtB,4BAA4B;IAC5B,cAAc,CAAC,EAAE,YAAY,CAAC;CAC/B;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,iCAAiC;IACjC,KAAK,EAAE,OAAO,CAAC;IACf,wBAAwB;IACxB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,0BAA0B;IAC1B,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB"}
package/dist/types.js ADDED
@@ -0,0 +1,4 @@
1
+ // ABOUTME: Core types for coordination modes
2
+ // ABOUTME: Defines CoordinationMode, ExecutionGraph, GraphNode, GraphEdge
3
+ export {};
4
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,0EAA0E"}
@@ -0,0 +1,30 @@
1
+ import type { ExecutionGraph, ValidationResult } from './types.js';
2
+ /**
3
+ * Validate an execution graph for correctness.
4
+ *
5
+ * Checks:
6
+ * - Entry node exists
7
+ * - All exit nodes exist
8
+ * - All edges reference valid nodes
9
+ * - All nodes are reachable from entry
10
+ * - No infinite cycles (cycles must have exit conditions)
11
+ *
12
+ * @param graph - The execution graph to validate
13
+ * @returns Validation result with errors and warnings
14
+ */
15
+ export declare function validateGraph(graph: ExecutionGraph): ValidationResult;
16
+ /**
17
+ * Check if a graph is acyclic.
18
+ *
19
+ * @param graph - The execution graph to check
20
+ * @returns true if the graph has no cycles
21
+ */
22
+ export declare function isAcyclic(graph: ExecutionGraph): boolean;
23
+ /**
24
+ * Get topological order of nodes in an acyclic graph.
25
+ *
26
+ * @param graph - The execution graph (must be acyclic)
27
+ * @returns Array of node IDs in topological order, or null if graph has cycles
28
+ */
29
+ export declare function getTopologicalOrder(graph: ExecutionGraph): string[] | null;
30
+ //# sourceMappingURL=validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAEnE;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,cAAc,GAAG,gBAAgB,CAmDrE;AA8GD;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CA4BxD;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,EAAE,GAAG,IAAI,CAwD1E"}
@@ -0,0 +1,236 @@
1
+ // ABOUTME: Graph validation for execution graphs
2
+ // ABOUTME: Validates structure, reachability, and detects problematic cycles
3
+ /**
4
+ * Validate an execution graph for correctness.
5
+ *
6
+ * Checks:
7
+ * - Entry node exists
8
+ * - All exit nodes exist
9
+ * - All edges reference valid nodes
10
+ * - All nodes are reachable from entry
11
+ * - No infinite cycles (cycles must have exit conditions)
12
+ *
13
+ * @param graph - The execution graph to validate
14
+ * @returns Validation result with errors and warnings
15
+ */
16
+ export function validateGraph(graph) {
17
+ const errors = [];
18
+ const warnings = [];
19
+ // Check entry node exists
20
+ if (!graph.nodes[graph.entryNode]) {
21
+ errors.push(`Entry node '${graph.entryNode}' not found in nodes`);
22
+ }
23
+ // Check exit nodes exist
24
+ for (const exitNode of graph.exitNodes) {
25
+ if (!graph.nodes[exitNode]) {
26
+ errors.push(`Exit node '${exitNode}' not found in nodes`);
27
+ }
28
+ }
29
+ // Check edges reference valid nodes
30
+ for (const edge of graph.edges) {
31
+ if (!graph.nodes[edge.from]) {
32
+ errors.push(`Edge references unknown source node '${edge.from}'`);
33
+ }
34
+ if (!graph.nodes[edge.to]) {
35
+ errors.push(`Edge references unknown target node '${edge.to}'`);
36
+ }
37
+ }
38
+ // Check all nodes are reachable from entry
39
+ const reachable = findReachableNodes(graph);
40
+ for (const nodeId of Object.keys(graph.nodes)) {
41
+ if (!reachable.has(nodeId)) {
42
+ warnings.push(`Node '${nodeId}' is not reachable from entry node`);
43
+ }
44
+ }
45
+ // Check for infinite cycles
46
+ const cycleIssues = detectInfiniteCycles(graph);
47
+ for (const issue of cycleIssues) {
48
+ errors.push(issue);
49
+ }
50
+ // Check that at least one exit node is reachable
51
+ const hasReachableExit = graph.exitNodes.some((exit) => reachable.has(exit));
52
+ if (!hasReachableExit && graph.exitNodes.length > 0) {
53
+ errors.push('No exit node is reachable from entry node');
54
+ }
55
+ return {
56
+ valid: errors.length === 0,
57
+ errors,
58
+ warnings,
59
+ };
60
+ }
61
+ /**
62
+ * Find all nodes reachable from the entry node.
63
+ */
64
+ function findReachableNodes(graph) {
65
+ const reachable = new Set();
66
+ const queue = [graph.entryNode];
67
+ while (queue.length > 0) {
68
+ const current = queue.shift();
69
+ if (reachable.has(current)) {
70
+ continue;
71
+ }
72
+ reachable.add(current);
73
+ // Find all edges from this node
74
+ for (const edge of graph.edges) {
75
+ if (edge.from === current && !reachable.has(edge.to)) {
76
+ queue.push(edge.to);
77
+ }
78
+ }
79
+ // Self-loops (nodes with maxIterations > 1)
80
+ const node = graph.nodes[current];
81
+ if (node?.maxIterations && node.maxIterations > 1) {
82
+ // Node can loop back to itself
83
+ }
84
+ }
85
+ return reachable;
86
+ }
87
+ /**
88
+ * Detect cycles that could cause infinite loops.
89
+ *
90
+ * A cycle is problematic if:
91
+ * - It has no conditional edges
92
+ * - Nodes in the cycle have no exit conditions
93
+ */
94
+ function detectInfiniteCycles(graph) {
95
+ const issues = [];
96
+ const visited = new Set();
97
+ const recursionStack = new Set();
98
+ function dfs(nodeId, path) {
99
+ if (recursionStack.has(nodeId)) {
100
+ // Found a cycle - check if it's safe
101
+ const cycleStart = path.indexOf(nodeId);
102
+ const cycle = path.slice(cycleStart);
103
+ // Check if any node in the cycle has an exit condition
104
+ let hasSafeExit = false;
105
+ for (const cycleNodeId of cycle) {
106
+ const node = graph.nodes[cycleNodeId];
107
+ if (node?.exitCondition) {
108
+ hasSafeExit = true;
109
+ break;
110
+ }
111
+ // Check if node has limited iterations
112
+ if (node?.maxIterations && node.maxIterations > 1 && node.maxIterations < Infinity) {
113
+ hasSafeExit = true;
114
+ break;
115
+ }
116
+ }
117
+ // Check if any edge in the cycle is conditional
118
+ for (let i = 0; i < cycle.length; i++) {
119
+ const from = cycle[i];
120
+ const to = cycle[(i + 1) % cycle.length];
121
+ const edge = graph.edges.find((e) => e.from === from && e.to === to);
122
+ if (edge?.condition) {
123
+ hasSafeExit = true;
124
+ break;
125
+ }
126
+ }
127
+ if (!hasSafeExit) {
128
+ issues.push(`Potential infinite cycle detected: ${cycle.join(' -> ')} -> ${nodeId}`);
129
+ }
130
+ return;
131
+ }
132
+ if (visited.has(nodeId)) {
133
+ return;
134
+ }
135
+ visited.add(nodeId);
136
+ recursionStack.add(nodeId);
137
+ // Visit all successors
138
+ for (const edge of graph.edges) {
139
+ if (edge.from === nodeId) {
140
+ dfs(edge.to, [...path, nodeId]);
141
+ }
142
+ }
143
+ recursionStack.delete(nodeId);
144
+ }
145
+ // Start DFS from entry node
146
+ if (graph.nodes[graph.entryNode]) {
147
+ dfs(graph.entryNode, []);
148
+ }
149
+ return issues;
150
+ }
151
+ /**
152
+ * Check if a graph is acyclic.
153
+ *
154
+ * @param graph - The execution graph to check
155
+ * @returns true if the graph has no cycles
156
+ */
157
+ export function isAcyclic(graph) {
158
+ const visited = new Set();
159
+ const recursionStack = new Set();
160
+ function hasCycle(nodeId) {
161
+ if (recursionStack.has(nodeId)) {
162
+ return true;
163
+ }
164
+ if (visited.has(nodeId)) {
165
+ return false;
166
+ }
167
+ visited.add(nodeId);
168
+ recursionStack.add(nodeId);
169
+ for (const edge of graph.edges) {
170
+ if (edge.from === nodeId) {
171
+ if (hasCycle(edge.to)) {
172
+ return true;
173
+ }
174
+ }
175
+ }
176
+ recursionStack.delete(nodeId);
177
+ return false;
178
+ }
179
+ return !hasCycle(graph.entryNode);
180
+ }
181
+ /**
182
+ * Get topological order of nodes in an acyclic graph.
183
+ *
184
+ * @param graph - The execution graph (must be acyclic)
185
+ * @returns Array of node IDs in topological order, or null if graph has cycles
186
+ */
187
+ export function getTopologicalOrder(graph) {
188
+ if (!isAcyclic(graph)) {
189
+ return null;
190
+ }
191
+ const inDegree = {};
192
+ const order = [];
193
+ // Initialize in-degree for all nodes
194
+ for (const nodeId of Object.keys(graph.nodes)) {
195
+ inDegree[nodeId] = 0;
196
+ }
197
+ // Count in-degrees
198
+ for (const edge of graph.edges) {
199
+ const currentDegree = inDegree[edge.to];
200
+ if (currentDegree !== undefined) {
201
+ inDegree[edge.to] = currentDegree + 1;
202
+ }
203
+ }
204
+ // Start with nodes that have no incoming edges
205
+ const queue = [];
206
+ for (const [nodeId, degree] of Object.entries(inDegree)) {
207
+ if (degree === 0) {
208
+ queue.push(nodeId);
209
+ }
210
+ }
211
+ // Process queue
212
+ while (queue.length > 0) {
213
+ const current = queue.shift();
214
+ order.push(current);
215
+ // Decrease in-degree for successors
216
+ for (const edge of graph.edges) {
217
+ if (edge.from === current) {
218
+ const currentDegree = inDegree[edge.to];
219
+ if (currentDegree !== undefined) {
220
+ const newDegree = currentDegree - 1;
221
+ inDegree[edge.to] = newDegree;
222
+ if (newDegree === 0) {
223
+ queue.push(edge.to);
224
+ }
225
+ }
226
+ }
227
+ }
228
+ }
229
+ // If we processed all nodes, return order
230
+ if (order.length === Object.keys(graph.nodes).length) {
231
+ return order;
232
+ }
233
+ // Graph has a cycle (shouldn't happen since we checked isAcyclic)
234
+ return null;
235
+ }
236
+ //# sourceMappingURL=validation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.js","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@inf-minds/coordination-modes",
3
+ "version": "0.0.1",
4
+ "description": "Execution graph definitions for multi-mind coordination",
5
+ "license": "Apache-2.0",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "src"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsc",
22
+ "test": "vitest run",
23
+ "test:watch": "vitest",
24
+ "typecheck": "tsc --noEmit",
25
+ "lint": "echo 'No linter configured yet'"
26
+ },
27
+ "dependencies": {
28
+ "@inf-minds/mindkit": "workspace:*"
29
+ },
30
+ "devDependencies": {
31
+ "@types/node": "^20.0.0",
32
+ "typescript": "^5.3.0",
33
+ "vitest": "^2.0.0"
34
+ }
35
+ }
package/src/index.ts ADDED
@@ -0,0 +1,30 @@
1
+ // ABOUTME: Coordination modes package entry point
2
+ // ABOUTME: Exports types, validation, and built-in modes
3
+
4
+ // Type exports
5
+ export type {
6
+ MindRef,
7
+ GraphNode,
8
+ GraphEdge,
9
+ ExecutionGraph,
10
+ ArtifactMapping,
11
+ RoutingRules,
12
+ CoordinationMode,
13
+ ValidationResult,
14
+ } from './types.js';
15
+
16
+ // Validation exports
17
+ export {
18
+ validateGraph,
19
+ isAcyclic,
20
+ getTopologicalOrder,
21
+ } from './validation.js';
22
+
23
+ // Mode exports
24
+ export {
25
+ createPipelineMode,
26
+ type PipelineOptions,
27
+ type PipelineStep,
28
+ createManagerWorkersMode,
29
+ type ManagerWorkersOptions,
30
+ } from './modes/index.js';
@@ -0,0 +1,8 @@
1
+ // ABOUTME: Built-in coordination modes
2
+ // ABOUTME: Exports pipeline and manager-workers mode factories
3
+
4
+ export { createPipelineMode, type PipelineOptions, type PipelineStep } from './pipeline.js';
5
+ export {
6
+ createManagerWorkersMode,
7
+ type ManagerWorkersOptions,
8
+ } from './manager-workers.js';
@@ -0,0 +1,98 @@
1
+ // ABOUTME: Manager-workers coordination mode
2
+ // ABOUTME: Central manager delegates tasks to workers in a loop
3
+
4
+ import type { CoordinationMode, MindRef } from '../types.js';
5
+
6
+ /**
7
+ * Options for creating a manager-workers mode.
8
+ */
9
+ export interface ManagerWorkersOptions {
10
+ /** Name of the mode */
11
+ name?: string;
12
+ /** Description of the mode */
13
+ description?: string;
14
+ /** Mind for the manager role */
15
+ managerMind: MindRef;
16
+ /** Mind for the worker role */
17
+ workerMind: MindRef;
18
+ /** Maximum iterations for the manager loop (default: 10) */
19
+ maxIterations?: number;
20
+ /**
21
+ * Exit condition for the manager.
22
+ * Evaluated against manager's output.
23
+ * Default: 'output.allTasksComplete'
24
+ */
25
+ exitCondition?: string;
26
+ /**
27
+ * Delegation condition.
28
+ * If true, manager delegates to worker.
29
+ * Default: 'output.delegatedTask'
30
+ */
31
+ delegationCondition?: string;
32
+ }
33
+
34
+ /**
35
+ * Create a manager-workers coordination mode.
36
+ *
37
+ * The manager orchestrates work by delegating tasks to workers.
38
+ * After each worker completes, control returns to the manager
39
+ * who decides whether to delegate more work or complete.
40
+ *
41
+ * ```
42
+ * +--> worker --+
43
+ * | |
44
+ * --> manager <------+
45
+ * |
46
+ * +--> [exit when complete]
47
+ * ```
48
+ *
49
+ * @param options - Manager-workers configuration
50
+ * @returns A CoordinationMode for manager-workers pattern
51
+ */
52
+ export function createManagerWorkersMode(options: ManagerWorkersOptions): CoordinationMode {
53
+ const {
54
+ name = 'manager-workers',
55
+ description,
56
+ managerMind,
57
+ workerMind,
58
+ maxIterations = 10,
59
+ exitCondition = 'output.allTasksComplete',
60
+ delegationCondition = 'output.delegatedTask',
61
+ } = options;
62
+
63
+ return {
64
+ name,
65
+ description,
66
+ graph: {
67
+ nodes: {
68
+ manager: {
69
+ id: 'manager',
70
+ mind: managerMind,
71
+ maxIterations,
72
+ exitCondition,
73
+ },
74
+ worker: {
75
+ id: 'worker',
76
+ mind: workerMind,
77
+ },
78
+ },
79
+ edges: [
80
+ {
81
+ from: 'manager',
82
+ to: 'worker',
83
+ condition: delegationCondition,
84
+ },
85
+ {
86
+ from: 'worker',
87
+ to: 'manager',
88
+ },
89
+ ],
90
+ entryNode: 'manager',
91
+ exitNodes: ['manager'],
92
+ },
93
+ defaultRouting: {
94
+ defaultVisibility: 'session',
95
+ autoPropagateArtifacts: true,
96
+ },
97
+ };
98
+ }
@@ -0,0 +1,86 @@
1
+ // ABOUTME: Pipeline coordination mode
2
+ // ABOUTME: Sequential execution of minds in a linear chain
3
+
4
+ import type { CoordinationMode, MindRef, GraphNode, GraphEdge } from '../types.js';
5
+
6
+ /**
7
+ * A step in the pipeline.
8
+ */
9
+ export interface PipelineStep {
10
+ /** Unique identifier for this step */
11
+ id: string;
12
+ /** Mind to execute at this step */
13
+ mind: MindRef;
14
+ }
15
+
16
+ /**
17
+ * Options for creating a pipeline mode.
18
+ */
19
+ export interface PipelineOptions {
20
+ /** Name of the pipeline */
21
+ name?: string;
22
+ /** Description of the pipeline */
23
+ description?: string;
24
+ /** Steps in the pipeline */
25
+ steps: PipelineStep[];
26
+ }
27
+
28
+ /**
29
+ * Create a pipeline coordination mode.
30
+ *
31
+ * A pipeline executes minds sequentially, passing artifacts
32
+ * from one step to the next.
33
+ *
34
+ * ```
35
+ * step1 -> step2 -> step3 -> ... -> stepN
36
+ * ```
37
+ *
38
+ * @param options - Pipeline configuration
39
+ * @returns A CoordinationMode for sequential execution
40
+ */
41
+ export function createPipelineMode(options: PipelineOptions): CoordinationMode {
42
+ const { steps, name = 'pipeline', description } = options;
43
+
44
+ if (steps.length === 0) {
45
+ throw new Error('Pipeline must have at least one step');
46
+ }
47
+
48
+ // Build nodes
49
+ const nodes: Record<string, GraphNode> = {};
50
+ for (const step of steps) {
51
+ nodes[step.id] = {
52
+ id: step.id,
53
+ mind: step.mind,
54
+ };
55
+ }
56
+
57
+ // Build edges (connect each step to the next)
58
+ const edges: GraphEdge[] = [];
59
+ for (let i = 0; i < steps.length - 1; i++) {
60
+ const current = steps[i]!;
61
+ const next = steps[i + 1]!;
62
+ edges.push({
63
+ from: current.id,
64
+ to: next.id,
65
+ });
66
+ }
67
+
68
+ // Safe to assert since we checked steps.length > 0
69
+ const firstStep = steps[0]!;
70
+ const lastStep = steps[steps.length - 1]!;
71
+
72
+ return {
73
+ name,
74
+ description,
75
+ graph: {
76
+ nodes,
77
+ edges,
78
+ entryNode: firstStep.id,
79
+ exitNodes: [lastStep.id],
80
+ },
81
+ defaultRouting: {
82
+ defaultVisibility: 'session',
83
+ autoPropagateArtifacts: true,
84
+ },
85
+ };
86
+ }
package/src/types.ts ADDED
@@ -0,0 +1,118 @@
1
+ // ABOUTME: Core types for coordination modes
2
+ // ABOUTME: Defines CoordinationMode, ExecutionGraph, GraphNode, GraphEdge
3
+
4
+ import type { MindConfig } from '@inf-minds/mindkit';
5
+
6
+ /**
7
+ * Reference to a mind configuration.
8
+ * Can be inline (with full config) or registered (by name).
9
+ */
10
+ export interface MindRef {
11
+ /** Type of reference */
12
+ type: 'inline' | 'registered';
13
+ /** Full mind configuration (for inline type) */
14
+ config?: MindConfig;
15
+ /** Name of registered mind type (for registered type) */
16
+ mindType?: string;
17
+ }
18
+
19
+ /**
20
+ * A node in the execution graph.
21
+ */
22
+ export interface GraphNode {
23
+ /** Unique identifier for this node */
24
+ id: string;
25
+ /** Mind to execute at this node */
26
+ mind: MindRef;
27
+ /** Maximum iterations for loops (default: 1) */
28
+ maxIterations?: number;
29
+ /**
30
+ * Exit condition expression.
31
+ * Evaluated against the node's output.
32
+ * If true, the node exits normally.
33
+ * If false and maxIterations not reached, the node re-executes.
34
+ */
35
+ exitCondition?: string;
36
+ }
37
+
38
+ /**
39
+ * Artifact mapping for edge transitions.
40
+ */
41
+ export interface ArtifactMapping {
42
+ /** Path patterns to include */
43
+ include?: string[];
44
+ /** Path patterns to exclude */
45
+ exclude?: string[];
46
+ /** Rename mappings (from -> to) */
47
+ rename?: Record<string, string>;
48
+ }
49
+
50
+ /**
51
+ * An edge connecting two nodes in the execution graph.
52
+ */
53
+ export interface GraphEdge {
54
+ /** Source node ID */
55
+ from: string;
56
+ /** Target node ID */
57
+ to: string;
58
+ /**
59
+ * Condition expression for conditional routing.
60
+ * Evaluated against the source node's output.
61
+ * If true (or no condition), the edge is traversed.
62
+ */
63
+ condition?: string;
64
+ /** Artifact mapping for this edge */
65
+ artifactMapping?: ArtifactMapping;
66
+ /** Named channel for artifact routing */
67
+ channel?: string;
68
+ }
69
+
70
+ /**
71
+ * The complete execution graph.
72
+ */
73
+ export interface ExecutionGraph {
74
+ /** All nodes in the graph */
75
+ nodes: Record<string, GraphNode>;
76
+ /** All edges connecting nodes */
77
+ edges: GraphEdge[];
78
+ /** ID of the entry node */
79
+ entryNode: string;
80
+ /** IDs of exit nodes (graph completes when one is reached) */
81
+ exitNodes: string[];
82
+ }
83
+
84
+ /**
85
+ * Default routing rules for artifacts.
86
+ */
87
+ export interface RoutingRules {
88
+ /** Default visibility for artifacts */
89
+ defaultVisibility?: 'private' | 'session' | 'persistent';
90
+ /** Auto-propagate artifacts along edges */
91
+ autoPropagateArtifacts?: boolean;
92
+ }
93
+
94
+ /**
95
+ * A coordination mode defines how multiple minds work together.
96
+ */
97
+ export interface CoordinationMode {
98
+ /** Mode name */
99
+ name: string;
100
+ /** Mode description */
101
+ description?: string;
102
+ /** The execution graph */
103
+ graph: ExecutionGraph;
104
+ /** Default routing rules */
105
+ defaultRouting?: RoutingRules;
106
+ }
107
+
108
+ /**
109
+ * Result of graph validation.
110
+ */
111
+ export interface ValidationResult {
112
+ /** Whether the graph is valid */
113
+ valid: boolean;
114
+ /** Validation errors */
115
+ errors: string[];
116
+ /** Validation warnings */
117
+ warnings: string[];
118
+ }
@@ -0,0 +1,278 @@
1
+ // ABOUTME: Graph validation for execution graphs
2
+ // ABOUTME: Validates structure, reachability, and detects problematic cycles
3
+
4
+ import type { ExecutionGraph, ValidationResult } from './types.js';
5
+
6
+ /**
7
+ * Validate an execution graph for correctness.
8
+ *
9
+ * Checks:
10
+ * - Entry node exists
11
+ * - All exit nodes exist
12
+ * - All edges reference valid nodes
13
+ * - All nodes are reachable from entry
14
+ * - No infinite cycles (cycles must have exit conditions)
15
+ *
16
+ * @param graph - The execution graph to validate
17
+ * @returns Validation result with errors and warnings
18
+ */
19
+ export function validateGraph(graph: ExecutionGraph): ValidationResult {
20
+ const errors: string[] = [];
21
+ const warnings: string[] = [];
22
+
23
+ // Check entry node exists
24
+ if (!graph.nodes[graph.entryNode]) {
25
+ errors.push(`Entry node '${graph.entryNode}' not found in nodes`);
26
+ }
27
+
28
+ // Check exit nodes exist
29
+ for (const exitNode of graph.exitNodes) {
30
+ if (!graph.nodes[exitNode]) {
31
+ errors.push(`Exit node '${exitNode}' not found in nodes`);
32
+ }
33
+ }
34
+
35
+ // Check edges reference valid nodes
36
+ for (const edge of graph.edges) {
37
+ if (!graph.nodes[edge.from]) {
38
+ errors.push(`Edge references unknown source node '${edge.from}'`);
39
+ }
40
+ if (!graph.nodes[edge.to]) {
41
+ errors.push(`Edge references unknown target node '${edge.to}'`);
42
+ }
43
+ }
44
+
45
+ // Check all nodes are reachable from entry
46
+ const reachable = findReachableNodes(graph);
47
+ for (const nodeId of Object.keys(graph.nodes)) {
48
+ if (!reachable.has(nodeId)) {
49
+ warnings.push(`Node '${nodeId}' is not reachable from entry node`);
50
+ }
51
+ }
52
+
53
+ // Check for infinite cycles
54
+ const cycleIssues = detectInfiniteCycles(graph);
55
+ for (const issue of cycleIssues) {
56
+ errors.push(issue);
57
+ }
58
+
59
+ // Check that at least one exit node is reachable
60
+ const hasReachableExit = graph.exitNodes.some((exit) => reachable.has(exit));
61
+ if (!hasReachableExit && graph.exitNodes.length > 0) {
62
+ errors.push('No exit node is reachable from entry node');
63
+ }
64
+
65
+ return {
66
+ valid: errors.length === 0,
67
+ errors,
68
+ warnings,
69
+ };
70
+ }
71
+
72
+ /**
73
+ * Find all nodes reachable from the entry node.
74
+ */
75
+ function findReachableNodes(graph: ExecutionGraph): Set<string> {
76
+ const reachable = new Set<string>();
77
+ const queue: string[] = [graph.entryNode];
78
+
79
+ while (queue.length > 0) {
80
+ const current = queue.shift()!;
81
+
82
+ if (reachable.has(current)) {
83
+ continue;
84
+ }
85
+
86
+ reachable.add(current);
87
+
88
+ // Find all edges from this node
89
+ for (const edge of graph.edges) {
90
+ if (edge.from === current && !reachable.has(edge.to)) {
91
+ queue.push(edge.to);
92
+ }
93
+ }
94
+
95
+ // Self-loops (nodes with maxIterations > 1)
96
+ const node = graph.nodes[current];
97
+ if (node?.maxIterations && node.maxIterations > 1) {
98
+ // Node can loop back to itself
99
+ }
100
+ }
101
+
102
+ return reachable;
103
+ }
104
+
105
+ /**
106
+ * Detect cycles that could cause infinite loops.
107
+ *
108
+ * A cycle is problematic if:
109
+ * - It has no conditional edges
110
+ * - Nodes in the cycle have no exit conditions
111
+ */
112
+ function detectInfiniteCycles(graph: ExecutionGraph): string[] {
113
+ const issues: string[] = [];
114
+ const visited = new Set<string>();
115
+ const recursionStack = new Set<string>();
116
+
117
+ function dfs(nodeId: string, path: string[]): void {
118
+ if (recursionStack.has(nodeId)) {
119
+ // Found a cycle - check if it's safe
120
+ const cycleStart = path.indexOf(nodeId);
121
+ const cycle = path.slice(cycleStart);
122
+
123
+ // Check if any node in the cycle has an exit condition
124
+ let hasSafeExit = false;
125
+ for (const cycleNodeId of cycle) {
126
+ const node = graph.nodes[cycleNodeId];
127
+ if (node?.exitCondition) {
128
+ hasSafeExit = true;
129
+ break;
130
+ }
131
+ // Check if node has limited iterations
132
+ if (node?.maxIterations && node.maxIterations > 1 && node.maxIterations < Infinity) {
133
+ hasSafeExit = true;
134
+ break;
135
+ }
136
+ }
137
+
138
+ // Check if any edge in the cycle is conditional
139
+ for (let i = 0; i < cycle.length; i++) {
140
+ const from = cycle[i];
141
+ const to = cycle[(i + 1) % cycle.length];
142
+ const edge = graph.edges.find((e) => e.from === from && e.to === to);
143
+ if (edge?.condition) {
144
+ hasSafeExit = true;
145
+ break;
146
+ }
147
+ }
148
+
149
+ if (!hasSafeExit) {
150
+ issues.push(`Potential infinite cycle detected: ${cycle.join(' -> ')} -> ${nodeId}`);
151
+ }
152
+ return;
153
+ }
154
+
155
+ if (visited.has(nodeId)) {
156
+ return;
157
+ }
158
+
159
+ visited.add(nodeId);
160
+ recursionStack.add(nodeId);
161
+
162
+ // Visit all successors
163
+ for (const edge of graph.edges) {
164
+ if (edge.from === nodeId) {
165
+ dfs(edge.to, [...path, nodeId]);
166
+ }
167
+ }
168
+
169
+ recursionStack.delete(nodeId);
170
+ }
171
+
172
+ // Start DFS from entry node
173
+ if (graph.nodes[graph.entryNode]) {
174
+ dfs(graph.entryNode, []);
175
+ }
176
+
177
+ return issues;
178
+ }
179
+
180
+ /**
181
+ * Check if a graph is acyclic.
182
+ *
183
+ * @param graph - The execution graph to check
184
+ * @returns true if the graph has no cycles
185
+ */
186
+ export function isAcyclic(graph: ExecutionGraph): boolean {
187
+ const visited = new Set<string>();
188
+ const recursionStack = new Set<string>();
189
+
190
+ function hasCycle(nodeId: string): boolean {
191
+ if (recursionStack.has(nodeId)) {
192
+ return true;
193
+ }
194
+ if (visited.has(nodeId)) {
195
+ return false;
196
+ }
197
+
198
+ visited.add(nodeId);
199
+ recursionStack.add(nodeId);
200
+
201
+ for (const edge of graph.edges) {
202
+ if (edge.from === nodeId) {
203
+ if (hasCycle(edge.to)) {
204
+ return true;
205
+ }
206
+ }
207
+ }
208
+
209
+ recursionStack.delete(nodeId);
210
+ return false;
211
+ }
212
+
213
+ return !hasCycle(graph.entryNode);
214
+ }
215
+
216
+ /**
217
+ * Get topological order of nodes in an acyclic graph.
218
+ *
219
+ * @param graph - The execution graph (must be acyclic)
220
+ * @returns Array of node IDs in topological order, or null if graph has cycles
221
+ */
222
+ export function getTopologicalOrder(graph: ExecutionGraph): string[] | null {
223
+ if (!isAcyclic(graph)) {
224
+ return null;
225
+ }
226
+
227
+ const inDegree: Record<string, number> = {};
228
+ const order: string[] = [];
229
+
230
+ // Initialize in-degree for all nodes
231
+ for (const nodeId of Object.keys(graph.nodes)) {
232
+ inDegree[nodeId] = 0;
233
+ }
234
+
235
+ // Count in-degrees
236
+ for (const edge of graph.edges) {
237
+ const currentDegree = inDegree[edge.to];
238
+ if (currentDegree !== undefined) {
239
+ inDegree[edge.to] = currentDegree + 1;
240
+ }
241
+ }
242
+
243
+ // Start with nodes that have no incoming edges
244
+ const queue: string[] = [];
245
+ for (const [nodeId, degree] of Object.entries(inDegree)) {
246
+ if (degree === 0) {
247
+ queue.push(nodeId);
248
+ }
249
+ }
250
+
251
+ // Process queue
252
+ while (queue.length > 0) {
253
+ const current = queue.shift()!;
254
+ order.push(current);
255
+
256
+ // Decrease in-degree for successors
257
+ for (const edge of graph.edges) {
258
+ if (edge.from === current) {
259
+ const currentDegree = inDegree[edge.to];
260
+ if (currentDegree !== undefined) {
261
+ const newDegree = currentDegree - 1;
262
+ inDegree[edge.to] = newDegree;
263
+ if (newDegree === 0) {
264
+ queue.push(edge.to);
265
+ }
266
+ }
267
+ }
268
+ }
269
+ }
270
+
271
+ // If we processed all nodes, return order
272
+ if (order.length === Object.keys(graph.nodes).length) {
273
+ return order;
274
+ }
275
+
276
+ // Graph has a cycle (shouldn't happen since we checked isAcyclic)
277
+ return null;
278
+ }