@ddse/acm-examples 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +113 -0
  3. package/bin/acm-demo.ts +495 -0
  4. package/data/coaching/agents.json +16 -0
  5. package/data/coaching/transcripts.json +37 -0
  6. package/data/documents.json +72 -0
  7. package/data/entitlement/customers.json +38 -0
  8. package/data/entitlement/policies.json +38 -0
  9. package/data/incidents/incidents.json +30 -0
  10. package/data/incidents/routing_rules.json +36 -0
  11. package/data/invoices/invoices.json +38 -0
  12. package/data/invoices/purchase-orders.json +38 -0
  13. package/data/issues.json +99 -0
  14. package/data/knowledge/docs/kb-001.md +7 -0
  15. package/data/knowledge/docs/kb-002.md +7 -0
  16. package/data/knowledge/docs/kb-003.md +9 -0
  17. package/data/knowledge/index.json +25 -0
  18. package/data/orders.json +106 -0
  19. package/dist/bin/acm-demo.d.ts +3 -0
  20. package/dist/bin/acm-demo.d.ts.map +1 -0
  21. package/dist/bin/acm-demo.js +392 -0
  22. package/dist/bin/acm-demo.js.map +1 -0
  23. package/dist/src/context/directives.d.ts +3 -0
  24. package/dist/src/context/directives.d.ts.map +1 -0
  25. package/dist/src/context/directives.js +325 -0
  26. package/dist/src/context/directives.js.map +1 -0
  27. package/dist/src/context/index.d.ts +2 -0
  28. package/dist/src/context/index.d.ts.map +1 -0
  29. package/dist/src/context/index.js +2 -0
  30. package/dist/src/context/index.js.map +1 -0
  31. package/dist/src/data/coaching.d.ts +19 -0
  32. package/dist/src/data/coaching.d.ts.map +1 -0
  33. package/dist/src/data/coaching.js +22 -0
  34. package/dist/src/data/coaching.js.map +1 -0
  35. package/dist/src/data/entitlement.d.ts +25 -0
  36. package/dist/src/data/entitlement.d.ts.map +1 -0
  37. package/dist/src/data/entitlement.js +26 -0
  38. package/dist/src/data/entitlement.js.map +1 -0
  39. package/dist/src/data/incidents.d.ts +23 -0
  40. package/dist/src/data/incidents.d.ts.map +1 -0
  41. package/dist/src/data/incidents.js +37 -0
  42. package/dist/src/data/incidents.js.map +1 -0
  43. package/dist/src/data/invoices.d.ts +34 -0
  44. package/dist/src/data/invoices.d.ts.map +1 -0
  45. package/dist/src/data/invoices.js +49 -0
  46. package/dist/src/data/invoices.js.map +1 -0
  47. package/dist/src/data/knowledge.d.ts +11 -0
  48. package/dist/src/data/knowledge.d.ts.map +1 -0
  49. package/dist/src/data/knowledge.js +57 -0
  50. package/dist/src/data/knowledge.js.map +1 -0
  51. package/dist/src/data/loader.d.ts +4 -0
  52. package/dist/src/data/loader.d.ts.map +1 -0
  53. package/dist/src/data/loader.js +69 -0
  54. package/dist/src/data/loader.js.map +1 -0
  55. package/dist/src/examples/scenarios.d.ts +23 -0
  56. package/dist/src/examples/scenarios.d.ts.map +1 -0
  57. package/dist/src/examples/scenarios.js +609 -0
  58. package/dist/src/examples/scenarios.js.map +1 -0
  59. package/dist/src/goals/index.d.ts +8 -0
  60. package/dist/src/goals/index.d.ts.map +1 -0
  61. package/dist/src/goals/index.js +12 -0
  62. package/dist/src/goals/index.js.map +1 -0
  63. package/dist/src/policy.d.ts +5 -0
  64. package/dist/src/policy.d.ts.map +1 -0
  65. package/dist/src/policy.js +24 -0
  66. package/dist/src/policy.js.map +1 -0
  67. package/dist/src/registries.d.ts +18 -0
  68. package/dist/src/registries.d.ts.map +1 -0
  69. package/dist/src/registries.js +38 -0
  70. package/dist/src/registries.js.map +1 -0
  71. package/dist/src/renderer.d.ts +9 -0
  72. package/dist/src/renderer.d.ts.map +1 -0
  73. package/dist/src/renderer.js +76 -0
  74. package/dist/src/renderer.js.map +1 -0
  75. package/dist/src/search/bm25.d.ts +68 -0
  76. package/dist/src/search/bm25.d.ts.map +1 -0
  77. package/dist/src/search/bm25.js +131 -0
  78. package/dist/src/search/bm25.js.map +1 -0
  79. package/dist/src/search/index.d.ts +2 -0
  80. package/dist/src/search/index.d.ts.map +1 -0
  81. package/dist/src/search/index.js +3 -0
  82. package/dist/src/search/index.js.map +1 -0
  83. package/dist/src/tasks/coaching.d.ts +30 -0
  84. package/dist/src/tasks/coaching.d.ts.map +1 -0
  85. package/dist/src/tasks/coaching.js +143 -0
  86. package/dist/src/tasks/coaching.js.map +1 -0
  87. package/dist/src/tasks/entitlement.d.ts +29 -0
  88. package/dist/src/tasks/entitlement.d.ts.map +1 -0
  89. package/dist/src/tasks/entitlement.js +135 -0
  90. package/dist/src/tasks/entitlement.js.map +1 -0
  91. package/dist/src/tasks/incidents.d.ts +42 -0
  92. package/dist/src/tasks/incidents.d.ts.map +1 -0
  93. package/dist/src/tasks/incidents.js +189 -0
  94. package/dist/src/tasks/incidents.js.map +1 -0
  95. package/dist/src/tasks/index.d.ts +7 -0
  96. package/dist/src/tasks/index.d.ts.map +1 -0
  97. package/dist/src/tasks/index.js +7 -0
  98. package/dist/src/tasks/index.js.map +1 -0
  99. package/dist/src/tasks/invoices.d.ts +40 -0
  100. package/dist/src/tasks/invoices.d.ts.map +1 -0
  101. package/dist/src/tasks/invoices.js +180 -0
  102. package/dist/src/tasks/invoices.js.map +1 -0
  103. package/dist/src/tasks/knowledge.d.ts +23 -0
  104. package/dist/src/tasks/knowledge.d.ts.map +1 -0
  105. package/dist/src/tasks/knowledge.js +115 -0
  106. package/dist/src/tasks/knowledge.js.map +1 -0
  107. package/dist/src/tasks/legacy.d.ts +50 -0
  108. package/dist/src/tasks/legacy.d.ts.map +1 -0
  109. package/dist/src/tasks/legacy.js +85 -0
  110. package/dist/src/tasks/legacy.js.map +1 -0
  111. package/dist/src/tools/coaching/index.d.ts +49 -0
  112. package/dist/src/tools/coaching/index.d.ts.map +1 -0
  113. package/dist/src/tools/coaching/index.js +119 -0
  114. package/dist/src/tools/coaching/index.js.map +1 -0
  115. package/dist/src/tools/entitlement/index.d.ts +52 -0
  116. package/dist/src/tools/entitlement/index.d.ts.map +1 -0
  117. package/dist/src/tools/entitlement/index.js +120 -0
  118. package/dist/src/tools/entitlement/index.js.map +1 -0
  119. package/dist/src/tools/incidents/index.d.ts +55 -0
  120. package/dist/src/tools/incidents/index.d.ts.map +1 -0
  121. package/dist/src/tools/incidents/index.js +109 -0
  122. package/dist/src/tools/incidents/index.js.map +1 -0
  123. package/dist/src/tools/index.d.ts +90 -0
  124. package/dist/src/tools/index.d.ts.map +1 -0
  125. package/dist/src/tools/index.js +109 -0
  126. package/dist/src/tools/index.js.map +1 -0
  127. package/dist/src/tools/invoices/index.d.ts +56 -0
  128. package/dist/src/tools/invoices/index.d.ts.map +1 -0
  129. package/dist/src/tools/invoices/index.js +85 -0
  130. package/dist/src/tools/invoices/index.js.map +1 -0
  131. package/dist/src/tools/knowledge/index.d.ts +52 -0
  132. package/dist/src/tools/knowledge/index.d.ts.map +1 -0
  133. package/dist/src/tools/knowledge/index.js +120 -0
  134. package/dist/src/tools/knowledge/index.js.map +1 -0
  135. package/dist/tests/bm25.test.d.ts +2 -0
  136. package/dist/tests/bm25.test.d.ts.map +1 -0
  137. package/dist/tests/bm25.test.js +98 -0
  138. package/dist/tests/bm25.test.js.map +1 -0
  139. package/dist/tests/integration.test.d.ts +2 -0
  140. package/dist/tests/integration.test.d.ts.map +1 -0
  141. package/dist/tests/integration.test.js +126 -0
  142. package/dist/tests/integration.test.js.map +1 -0
  143. package/dist/tests/plan-hydration.test.d.ts +2 -0
  144. package/dist/tests/plan-hydration.test.d.ts.map +1 -0
  145. package/dist/tests/plan-hydration.test.js +28 -0
  146. package/dist/tests/plan-hydration.test.js.map +1 -0
  147. package/dist/tsconfig.tsbuildinfo +1 -0
  148. package/docs/examples-architecture.md +144 -0
  149. package/docs/successrun.md +1022 -0
  150. package/package.json +33 -0
  151. package/src/context/directives.ts +366 -0
  152. package/src/context/index.ts +1 -0
  153. package/src/data/coaching.ts +50 -0
  154. package/src/data/entitlement.ts +60 -0
  155. package/src/data/incidents.ts +78 -0
  156. package/src/data/invoices.ts +103 -0
  157. package/src/data/knowledge.ts +77 -0
  158. package/src/data/loader.ts +80 -0
  159. package/src/examples/scenarios.ts +724 -0
  160. package/src/goals/index.ts +18 -0
  161. package/src/policy.ts +30 -0
  162. package/src/registries.ts +48 -0
  163. package/src/renderer.ts +82 -0
  164. package/src/search/bm25.ts +173 -0
  165. package/src/search/index.ts +2 -0
  166. package/src/tasks/coaching.ts +217 -0
  167. package/src/tasks/entitlement.ts +197 -0
  168. package/src/tasks/incidents.ts +277 -0
  169. package/src/tasks/index.ts +6 -0
  170. package/src/tasks/invoices.ts +269 -0
  171. package/src/tasks/knowledge.ts +169 -0
  172. package/src/tasks/legacy.ts +112 -0
  173. package/src/tools/coaching/index.ts +197 -0
  174. package/src/tools/entitlement/index.ts +199 -0
  175. package/src/tools/incidents/index.ts +185 -0
  176. package/src/tools/index.ts +192 -0
  177. package/src/tools/invoices/index.ts +165 -0
  178. package/src/tools/knowledge/index.ts +203 -0
  179. package/tests/bm25.test.ts +129 -0
  180. package/tests/integration.test.ts +163 -0
  181. package/tests/plan-hydration.test.ts +33 -0
  182. package/tsconfig.json +18 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 DDSE Foundation
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,113 @@
1
+ # @ddse/acm-examples
2
+
3
+ End-to-end ACM demos showcasing deterministic scenarios, streaming execution, and replay capture for the Node.js framework helper.
4
+
5
+ ## What this package gives you
6
+
7
+ - **Scenario-driven CLI** (`bin/acm-demo.ts`) that drives the ACM Framework end-to-end with the structured planner, streaming UI, and resume-friendly execution.
8
+ - **Five production-style workflows** (entitlement, knowledge, incidents, invoices, coaching) with curated data, tools, and capability registries.
9
+ - **Streaming UX** via `DefaultStreamSink` and `CLIRenderer` for planner tokens, task updates, and checkpoints.
10
+ - **Replay + resume support** through file-backed checkpoints and replay bundle export.
11
+ - **Integration tests** that exercise every scenario with a stubbed LLM so you can validate changes quickly.
12
+
13
+ ## Prerequisites
14
+
15
+ ```bash
16
+ pnpm install
17
+ pnpm --filter @ddse/acm-examples build
18
+ ```
19
+
20
+ ## Running the demo CLI
21
+
22
+ ### Quick start
23
+
24
+ ```bash
25
+ # Entitlement decisioning with default Ollama client
26
+ pnpm --filter @ddse/acm-examples demo -- --scenario entitlement
27
+
28
+ # Knowledge acceleration using vLLM
29
+ pnpm --filter @ddse/acm-examples demo -- --provider vllm --model qwen2.5:7b --scenario knowledge
30
+
31
+ # Incident triage with persistent checkpoints
32
+ pnpm --filter @ddse/acm-examples demo -- --scenario incidents --checkpoint-dir ./tmp/checkpoints
33
+
34
+ # Invoice reconciliation with replay bundle export
35
+ pnpm --filter @ddse/acm-examples demo -- --scenario invoices --save-bundle
36
+ ```
37
+
38
+ ### CLI options
39
+
40
+ ```text
41
+ --scenario <key> Scenario to execute (use --list to see options)
42
+ --list List available scenarios and exit
43
+ --provider <ollama|vllm> LLM provider (default: ollama)
44
+ --model <name> Model identifier (defaults per provider)
45
+ --base-url <url> Override LLM endpoint URL
46
+ --engine <acm|langgraph|msaf> Execution engine (default: acm)
47
+ --resume <runId> Resume a prior ACM engine run
48
+ --checkpoint-dir <dir> Directory for checkpoint storage (default: ./checkpoints)
49
+ --no-stream Disable live planner/task streaming
50
+ --save-bundle Save replay bundle to replay/<runId>/
51
+ -h, --help Show help
52
+ ```
53
+
54
+ ### Available scenarios
55
+
56
+ | Key | Name | Focus |
57
+ | --- | ---- | ----- |
58
+ | `entitlement` | Entitlement Decisioning | Fetch CRM data, evaluate benefit policy, and notify the supervisor. |
59
+ | `knowledge` | Knowledge Acceleration | Retrieve, summarize, and follow up on knowledge base content. |
60
+ | `incidents` | Incident Triage | Classify severity, choose routing queue, and escalate if needed. |
61
+ | `invoices` | Invoice Reconciliation | Compare invoice vs PO and log audit findings. |
62
+ | `coaching` | Agent Coaching | Analyze transcripts, craft feedback, and store coaching notes. |
63
+
64
+ Each scenario exposes `buildReferencePlan()` to generate a deterministic reference plan used by automated tests and replay exports while the CLI exercises the planner live with the same tools, capabilities, and assertions.
65
+
66
+ ## Streaming and verification
67
+
68
+ The CLI wires `DefaultStreamSink` into the `CLIRenderer` so you get:
69
+
70
+ - Real-time planner token streaming
71
+ - Task progress + step updates
72
+ - Checkpoint notifications during ACM engine runs
73
+ - Summary output once execution finishes
74
+
75
+ Verification expressions from each task are executed locally before continuing, ensuring deterministic guard rails across scenarios.
76
+
77
+ ## Integration tests
78
+
79
+ All workflows are validated in `tests/integration.test.ts` with a stubbed LLM and the same reference plans:
80
+
81
+ ```bash
82
+ pnpm --filter @ddse/acm-examples test
83
+ ```
84
+
85
+ Running the suite will iterate over every scenario, execute its plan, and assert the expected outputs—ideal for CI or for verifying modifications to tools/tasks.
86
+
87
+ ## Customization tips
88
+
89
+ - Add new tools or capabilities in `src/tools` and `src/tasks`, then register them inside a scenario definition.
90
+ - Use `ScenarioDefinition.buildReferencePlan()` as the place to define deterministic reference plans when adding scenarios.
91
+ - Extend the CLI by introducing new flags or output modes in `bin/acm-demo.ts`—it already exposes context registration, streaming, resume, and replay wiring.
92
+
93
+ ## Project layout
94
+
95
+ ```text
96
+ packages/acm-examples/
97
+ ├── bin/acm-demo.ts # Scenario-driven demo CLI
98
+ ├── src/
99
+ │ ├── context/ # Directive-based context providers
100
+ │ ├── data/ # Synthetic datasets per scenario
101
+ │ ├── examples/scenarios.ts # Scenario catalog & reference plans
102
+ │ ├── registries.ts # Simple tool & capability registries
103
+ │ ├── renderer.ts # Streaming CLI renderer
104
+ │ └── tasks/ & tools/ # Deterministic task/tool implementations
105
+ ├── tests/integration.test.ts # Full scenario regression suite
106
+ └── dist/ # Build artifacts (generated)
107
+ ```
108
+
109
+ ## Next steps
110
+
111
+ - Plug in your own LLM provider by extending `createOllamaClient`/`createVLLMClient` equivalents.
112
+ - Add new scenarios that exercise additional policies or data domains.
113
+ - Use replay bundles to capture and share successful runs for downstream analysis.
@@ -0,0 +1,495 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { parseArgs } from 'node:util';
4
+ import { createInterface } from 'node:readline/promises';
5
+ import { stdin as input, stdout as output, exit } from 'node:process';
6
+
7
+ import {
8
+ DefaultStreamSink,
9
+ ExternalContextProviderAdapter,
10
+ type LLMCallFn,
11
+ type Plan,
12
+ normalizePlan,
13
+ normalizePlannerResult,
14
+ type NormalizedPlannerResult,
15
+ } from '@ddse/acm-sdk';
16
+ import { MemoryLedger, FileCheckpointStore } from '@ddse/acm-runtime';
17
+ import { createOllamaClient, createVLLMClient } from '@ddse/acm-llm';
18
+ import type { LLM, ToolCall } from '@ddse/acm-llm';
19
+ import { ACMFramework, ExecutionEngine } from '@ddse/acm-framework';
20
+ import { ReplayBundleExporter, type TaskIORecord } from '@ddse/acm-replay';
21
+
22
+ import { SimpleCapabilityRegistry, SimpleToolRegistry } from '../src/registries.js';
23
+ import { SimplePolicyEngine } from '../src/policy.js';
24
+ import { CLIRenderer } from '../src/renderer.js';
25
+ import { registerExampleContextProviders } from '../src/context/index.js';
26
+ import { getScenario, listScenarioKeys, scenarios, type ScenarioDefinition } from '../src/examples/scenarios.js';
27
+
28
+ type LLMProvider = 'ollama' | 'vllm';
29
+
30
+ type CLIOptions = {
31
+ scenarioKey?: string;
32
+ provider: LLMProvider;
33
+ model: string;
34
+ baseUrl?: string;
35
+ engine: ExecutionEngine;
36
+ stream: boolean;
37
+ resume?: string;
38
+ checkpointDir: string;
39
+ saveBundle: boolean;
40
+ listOnly: boolean;
41
+ };
42
+
43
+ type RawCLIArgs = {
44
+ scenario?: string;
45
+ goal?: string;
46
+ provider?: string;
47
+ model?: string;
48
+ 'base-url'?: string;
49
+ engine?: string;
50
+ stream?: boolean;
51
+ resume?: string;
52
+ 'checkpoint-dir'?: string;
53
+ 'save-bundle'?: boolean;
54
+ list?: boolean;
55
+ help?: boolean;
56
+ };
57
+
58
+ type PlanTask = NonNullable<Plan['tasks']>[number];
59
+
60
+ const defaultModelByProvider: Record<LLMProvider, string> = {
61
+ ollama: 'llama3.1',
62
+ vllm: 'qwen2.5:7b',
63
+ };
64
+
65
+ function printHelp(): void {
66
+ console.log(
67
+ `ACM Demo CLI\n\n` +
68
+ `Usage:\n` +
69
+ ` acm-demo [options]\n\n` +
70
+ `Options:\n` +
71
+ ` --scenario <key> Scenario key to execute (${listScenarioKeys().join(', ')})\n` +
72
+ ` --list List available scenarios and exit\n` +
73
+ ` --provider <ollama|vllm> LLM provider (default: ollama)\n` +
74
+ ` --model <name> Model identifier (defaults per provider)\n` +
75
+ ` --base-url <url> Override LLM endpoint\n` +
76
+ ` --engine <acm|langgraph|msaf> Execution engine (default: acm)\n` +
77
+ ` --resume <runId> Resume a prior ACM engine run\n` +
78
+ ` --checkpoint-dir <dir> Directory for checkpoints (default: ./checkpoints)\n` +
79
+ ` --no-stream Disable streaming updates\n` +
80
+ ` --save-bundle Export a replay bundle to ./replay/<runId>/\n` +
81
+ ` -h, --help Show this help message\n`
82
+ );
83
+ }
84
+
85
+ function mapLegacyGoal(goal?: string): string | undefined {
86
+ if (!goal) return undefined;
87
+ switch (goal.toLowerCase()) {
88
+ case 'refund':
89
+ return 'entitlement';
90
+ case 'issues':
91
+ return 'knowledge';
92
+ default:
93
+ return goal;
94
+ }
95
+ }
96
+
97
+ function resolveExecutionEngine(engine?: string): ExecutionEngine {
98
+ if (!engine) {
99
+ return ExecutionEngine.ACM;
100
+ }
101
+
102
+ const normalized = engine.toUpperCase();
103
+ switch (normalized) {
104
+ case 'ACM':
105
+ return ExecutionEngine.ACM;
106
+ case 'LANGGRAPH':
107
+ case 'LANG-GRAPH':
108
+ case 'LANG_GRAPH':
109
+ return ExecutionEngine.LANGGRAPH;
110
+ case 'MSAF':
111
+ case 'MS_AGENT_FRAMEWORK':
112
+ case 'MS-AGENT-FRAMEWORK':
113
+ return ExecutionEngine.MSAF;
114
+ default:
115
+ throw new Error(`Unsupported execution engine: ${engine}`);
116
+ }
117
+ }
118
+
119
+ function parseCliOptions(): { options: CLIOptions; showHelp: boolean } {
120
+ const { values } = parseArgs({
121
+ options: {
122
+ scenario: { type: 'string' },
123
+ goal: { type: 'string' },
124
+ provider: { type: 'string' },
125
+ model: { type: 'string' },
126
+ 'base-url': { type: 'string' },
127
+ engine: { type: 'string' },
128
+ stream: { type: 'boolean', default: true },
129
+ resume: { type: 'string' },
130
+ 'checkpoint-dir': { type: 'string' },
131
+ 'save-bundle': { type: 'boolean', default: false },
132
+ list: { type: 'boolean', default: false },
133
+ help: { type: 'boolean', default: false },
134
+ },
135
+ allowPositionals: false,
136
+ }) as { values: RawCLIArgs };
137
+
138
+ const provider = (values.provider ?? 'ollama').toLowerCase();
139
+ if (provider !== 'ollama' && provider !== 'vllm') {
140
+ throw new Error(`Unsupported provider: ${values.provider}`);
141
+ }
142
+
143
+ const engine = resolveExecutionEngine(values.engine);
144
+
145
+ const scenarioKey = values.scenario ?? mapLegacyGoal(values.goal);
146
+ const model = values.model ?? defaultModelByProvider[provider as LLMProvider];
147
+ return {
148
+ options: {
149
+ scenarioKey,
150
+ provider: provider as LLMProvider,
151
+ model,
152
+ baseUrl: values['base-url'],
153
+ engine,
154
+ stream: values.stream ?? true,
155
+ resume: values.resume,
156
+ checkpointDir: values['checkpoint-dir'] ?? './checkpoints',
157
+ saveBundle: values['save-bundle'] ?? false,
158
+ listOnly: values.list ?? false,
159
+ },
160
+ showHelp: Boolean(values.help),
161
+ };
162
+ }
163
+
164
+ async function resolveScenario(options: CLIOptions): Promise<ScenarioDefinition> {
165
+ if (options.scenarioKey) {
166
+ const direct = getScenario(options.scenarioKey);
167
+ if (!direct) {
168
+ throw new Error(`Unknown scenario: ${options.scenarioKey}`);
169
+ }
170
+ return direct;
171
+ }
172
+
173
+ if (!output.isTTY) {
174
+ throw new Error('No scenario provided and terminal is non-interactive. Use --scenario <key>.');
175
+ }
176
+
177
+ console.log('Please choose a scenario to run:\n');
178
+ const keys = listScenarioKeys();
179
+ keys.forEach((key, index) => {
180
+ const scenario = scenarios[key];
181
+ console.log(` [${index + 1}] ${scenario.name} (${key})`);
182
+ console.log(` ${scenario.description}`);
183
+ });
184
+ console.log();
185
+
186
+ const rl = createInterface({ input, output });
187
+ try {
188
+ while (true) {
189
+ const answer = (await rl.question('Enter scenario number or key: ')).trim();
190
+ if (!answer) {
191
+ continue;
192
+ }
193
+
194
+ const numeric = Number.parseInt(answer, 10);
195
+ if (!Number.isNaN(numeric) && numeric >= 1 && numeric <= keys.length) {
196
+ return scenarios[keys[numeric - 1]];
197
+ }
198
+
199
+ const byKey = getScenario(answer);
200
+ if (byKey) {
201
+ return byKey;
202
+ }
203
+
204
+ console.log(`Invalid selection: ${answer}`);
205
+ }
206
+ } finally {
207
+ rl.close();
208
+ }
209
+ }
210
+
211
+ function createLLMClient(provider: LLMProvider, model: string, baseUrl?: string): LLM {
212
+ switch (provider) {
213
+ case 'ollama':
214
+ return createOllamaClient(model, baseUrl);
215
+ case 'vllm':
216
+ return createVLLMClient(model, baseUrl);
217
+ default:
218
+ throw new Error(`Unsupported provider: ${provider satisfies never}`);
219
+ }
220
+ }
221
+
222
+ function attachStreaming(renderer: CLIRenderer, stream: DefaultStreamSink): void {
223
+ stream.attach('planner', chunk => renderer.renderPlannerToken(chunk));
224
+ stream.attach('task', update => renderer.renderTaskUpdate(update));
225
+ stream.attach('checkpoint', update => {
226
+ if (typeof update?.checkpointId === 'string') {
227
+ console.log(`💾 Checkpoint created: ${update.checkpointId} (${update.tasksCompleted} tasks completed)`);
228
+ }
229
+ });
230
+ }
231
+
232
+
233
+ async function main(): Promise<void> {
234
+ const { options, showHelp } = parseCliOptions();
235
+
236
+ if (showHelp) {
237
+ printHelp();
238
+ return;
239
+ }
240
+
241
+ if (options.listOnly) {
242
+ console.log('Available scenarios:\n');
243
+ listScenarioKeys().forEach(key => {
244
+ const scenario = scenarios[key];
245
+ console.log(`- ${key}: ${scenario.name}`);
246
+ console.log(` ${scenario.description}`);
247
+ });
248
+ return;
249
+ }
250
+
251
+ const scenario = await resolveScenario(options);
252
+
253
+ console.log(`\n🎯 Scenario: ${scenario.name}`);
254
+ console.log(` Key: ${scenario.key}`);
255
+ console.log(` Description: ${scenario.description}\n`);
256
+
257
+ const toolRegistry = new SimpleToolRegistry();
258
+ const capabilityRegistry = new SimpleCapabilityRegistry();
259
+ scenario.registerTools(toolRegistry);
260
+ scenario.registerCapabilities(capabilityRegistry);
261
+
262
+ const adapter = new ExternalContextProviderAdapter();
263
+ registerExampleContextProviders(adapter);
264
+
265
+ const llm = createLLMClient(options.provider, options.model, options.baseUrl);
266
+ const stream = new DefaultStreamSink();
267
+ const renderer = new CLIRenderer();
268
+
269
+ if (options.stream) {
270
+ attachStreaming(renderer, stream);
271
+ }
272
+
273
+ const verify = async (taskId: string, output: any, expressions: string[]): Promise<boolean> => {
274
+ for (const expr of expressions) {
275
+ try {
276
+ const fn = new Function('output', `return ${expr};`);
277
+ const result = fn(output);
278
+ if (!result) {
279
+ console.error(`Verification failed for ${taskId}: ${expr}`);
280
+ return false;
281
+ }
282
+ } catch (error) {
283
+ console.error(`Verification error for ${taskId}: ${expr}`, error);
284
+ return false;
285
+ }
286
+ }
287
+ return true;
288
+ };
289
+
290
+ const nucleusLLMCall: LLMCallFn = async (prompt, tools, callConfig) => {
291
+ const toolDefs = tools.map(tool => ({
292
+ name: tool.name,
293
+ description: tool.description ?? 'Nucleus tool',
294
+ inputSchema: tool.inputSchema ?? { type: 'object', properties: {} },
295
+ }));
296
+
297
+ const response = await llm.generateWithTools!(
298
+ [
299
+ {
300
+ role: 'system',
301
+ content: prompt,
302
+ },
303
+ ],
304
+ toolDefs,
305
+ {
306
+ temperature: callConfig.temperature,
307
+ seed: callConfig.seed,
308
+ maxTokens: callConfig.maxTokens,
309
+ }
310
+ );
311
+
312
+ const responseToolCalls = (response.toolCalls ?? []) as ToolCall[];
313
+ const toolCalls = responseToolCalls.map(tc => ({
314
+ id: tc.id,
315
+ name: tc.name,
316
+ input: tc.arguments,
317
+ }));
318
+
319
+ return {
320
+ reasoning: response.text,
321
+ toolCalls,
322
+ raw: response.raw,
323
+ };
324
+ };
325
+
326
+ const nucleusConfig = {
327
+ llmCall: {
328
+ provider: llm.name(),
329
+ model: options.model,
330
+ temperature: 0.5,
331
+ maxTokens: 8096,
332
+ },
333
+ hooks: {
334
+ preflight: false,
335
+ postcheck: false,
336
+ },
337
+ } as const;
338
+
339
+ const framework = ACMFramework.create({
340
+ capabilityRegistry,
341
+ toolRegistry,
342
+ policyEngine: new SimplePolicyEngine(),
343
+ nucleus: {
344
+ call: nucleusLLMCall,
345
+ llmConfig: nucleusConfig.llmCall,
346
+ hooks: nucleusConfig.hooks,
347
+ },
348
+ verify,
349
+ defaultStream: options.stream ? stream : undefined,
350
+ contextProvider: adapter,
351
+ });
352
+
353
+ const ledger = new MemoryLedger();
354
+ const checkpointStore = new FileCheckpointStore(options.checkpointDir);
355
+ const runId = options.resume ?? `run-${Date.now()}`;
356
+ const startedAt = new Date().toISOString();
357
+
358
+ console.log('📋 Planning...\n');
359
+ const planResponse = await framework.plan({
360
+ goal: scenario.goal,
361
+ context: scenario.context,
362
+ stream: options.stream ? stream : undefined,
363
+ ledger,
364
+ });
365
+
366
+ const rawPlannerResult = planResponse.result;
367
+ const rawPlan = planResponse.selectedPlan;
368
+
369
+ if (!rawPlan) {
370
+ throw new Error('Structured planner did not return a plan');
371
+ }
372
+
373
+ const normalizeOptions = {
374
+ capabilityRegistry,
375
+ defaultContextRef: scenario.context.id ?? scenario.key,
376
+ planIdPrefix: `${scenario.key}-plan`,
377
+ } as const;
378
+
379
+ const plan = normalizePlan(rawPlan, normalizeOptions);
380
+ const plannerResult: NormalizedPlannerResult = normalizePlannerResult(
381
+ rawPlannerResult,
382
+ plan,
383
+ normalizeOptions
384
+ );
385
+
386
+ if (plannerResult?.rationale) {
387
+ console.log('🧠 Planner rationale:\n');
388
+ console.log(plannerResult.rationale);
389
+ console.log();
390
+ }
391
+
392
+ if (plannerResult?.plans?.length) {
393
+ console.log('📝 Planner plan summary (first plan):');
394
+ const firstPlan = plannerResult.plans[0];
395
+ console.log(JSON.stringify({
396
+ id: firstPlan.id,
397
+ contextRef: firstPlan.contextRef,
398
+ tasks: (firstPlan.tasks ?? []).map((task: PlanTask) => ({
399
+ id: task.id,
400
+ capability: task.capability ?? task.capabilityRef,
401
+ hasInput: Boolean(task.input && Object.keys(task.input).length > 0),
402
+ inputPreview: task.input ?? null,
403
+ })),
404
+ }, null, 2));
405
+ console.log();
406
+ }
407
+
408
+ console.log(`\n✅ Plans generated: ${plannerResult?.plans?.length ?? 0}`);
409
+ if (plan.id) {
410
+ console.log(`Executing plan ${plan.id}`);
411
+ }
412
+ console.log(`Context Ref: ${plan.contextRef ?? 'n/a'}`);
413
+ console.log('Tasks:');
414
+ plan.tasks?.forEach((task: PlanTask, index: number) => {
415
+ console.log(` ${index + 1}. ${task.id} -> ${task.capability ?? task.capabilityRef ?? 'unknown'}`);
416
+ });
417
+ console.log();
418
+
419
+ const executionEngine = options.engine;
420
+ if (executionEngine !== ExecutionEngine.ACM && options.resume) {
421
+ console.warn('⚠️ Resume is only supported with the ACM engine. Ignoring --resume.');
422
+ }
423
+
424
+ const executeResult = await framework.execute({
425
+ goal: scenario.goal,
426
+ context: scenario.context,
427
+ stream: options.stream ? stream : undefined,
428
+ engine: executionEngine,
429
+ ledger,
430
+ existingPlan: {
431
+ plan,
432
+ plannerResult,
433
+ },
434
+ resumeFrom: executionEngine === ExecutionEngine.ACM ? options.resume : undefined,
435
+ checkpointStore: executionEngine === ExecutionEngine.ACM ? checkpointStore : undefined,
436
+ checkpointInterval: executionEngine === ExecutionEngine.ACM ? 1 : undefined,
437
+ runId,
438
+ });
439
+
440
+ const completedAt = new Date().toISOString();
441
+
442
+ renderer.renderSummary(executeResult.execution);
443
+ scenario.assertExecution(executeResult.execution);
444
+
445
+ if (options.saveBundle) {
446
+ console.log('\n💾 Saving replay bundle...');
447
+ const taskIO: TaskIORecord[] = [];
448
+ for (const taskSpec of plan.tasks as PlanTask[]) {
449
+ const record = executeResult.execution.outputsByTask?.[taskSpec.id];
450
+ if (record?.output !== undefined) {
451
+ taskIO.push({
452
+ taskId: taskSpec.id,
453
+ capability: taskSpec.capabilityRef || taskSpec.capability || 'unknown',
454
+ input: taskSpec.input || {},
455
+ output: record.output,
456
+ ts: new Date().toISOString(),
457
+ });
458
+ }
459
+ }
460
+
461
+ const bundlePath = await ReplayBundleExporter.export({
462
+ outputDir: `./replay/${runId}`,
463
+ goal: scenario.goal,
464
+ context: scenario.context,
465
+ plans: plannerResult.plans,
466
+ selectedPlanId: plan.id,
467
+ ledger: Array.from(executeResult.execution.ledger) as any[],
468
+ taskIO,
469
+ engineTrace: {
470
+ runId,
471
+ engine: executionEngine,
472
+ startedAt,
473
+ completedAt,
474
+ status: 'success',
475
+ tasks: (plan.tasks as PlanTask[]).map(task => ({
476
+ taskId: task.id,
477
+ status: executeResult.execution.outputsByTask?.[task.id] ? 'completed' : 'skipped',
478
+ startedAt,
479
+ completedAt,
480
+ })),
481
+ },
482
+ });
483
+
484
+ console.log(` ✅ Bundle saved to: ${bundlePath}`);
485
+ }
486
+
487
+ console.log('\n✅ Demo completed successfully!');
488
+ }
489
+
490
+ if (import.meta.url === `file://${process.argv[1]}`) {
491
+ main().catch(error => {
492
+ console.error('Fatal error:', error);
493
+ exit(1);
494
+ });
495
+ }
@@ -0,0 +1,16 @@
1
+ [
2
+ {
3
+ "id": "AG-883",
4
+ "name": "Priya Singh",
5
+ "region": "AMER",
6
+ "tenureMonths": 28,
7
+ "managerEmail": "luis.mendez@northwind.example"
8
+ },
9
+ {
10
+ "id": "AG-905",
11
+ "name": "Jordan Lee",
12
+ "region": "EMEA",
13
+ "tenureMonths": 14,
14
+ "managerEmail": "ella.thompson@northwind.example"
15
+ }
16
+ ]
@@ -0,0 +1,37 @@
1
+ [
2
+ {
3
+ "id": "TRANS-5540",
4
+ "agentId": "AG-883",
5
+ "customerSentiment": "NEGATIVE",
6
+ "complianceFlags": ["DISCLOSURE_MISSED", "CALL_TAG_MISSING"],
7
+ "transcript": [
8
+ "Agent: Thank you for calling Northwind Support, this is Priya.",
9
+ "Customer: I've been waiting three days for someone to unblock my fulfillment feed.",
10
+ "Agent: I can see the backlog and I'm escalating to our ops team right now.",
11
+ "Customer: This is costing me money every hour.",
12
+ "Agent: I completely understand how frustrating that is and I'm logging an urgent ticket.",
13
+ "Agent: You'll get an update within the next two hours and I'll follow up personally.",
14
+ "Customer: Please make sure someone actually reaches out this time.",
15
+ "Agent: Absolutely, I'll send you confirmation as soon as I have it."
16
+ ],
17
+ "callDurationSeconds": 780,
18
+ "followUpRequired": true
19
+ },
20
+ {
21
+ "id": "TRANS-5581",
22
+ "agentId": "AG-905",
23
+ "customerSentiment": "NEUTRAL",
24
+ "complianceFlags": [],
25
+ "transcript": [
26
+ "Agent: Hi, this is Jordan with Northwind Coaching Desk.",
27
+ "Customer: I'm double-checking the renewal timeline for our premium license.",
28
+ "Agent: Happy to help—your renewal is scheduled for next month on the 18th.",
29
+ "Customer: Great, can you send me the quote details?",
30
+ "Agent: I'll email them right away and flag your account manager to follow up.",
31
+ "Customer: Perfect, thanks for the quick support.",
32
+ "Agent: Anytime! Let me know if there's anything else."
33
+ ],
34
+ "callDurationSeconds": 360,
35
+ "followUpRequired": false
36
+ }
37
+ ]