ai-runtime-engine 1.1.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 (269) hide show
  1. package/CHANGELOG.md +78 -0
  2. package/LICENSE +15 -0
  3. package/README.md +489 -0
  4. package/dist/artifacts/artifacts.d.ts +28 -0
  5. package/dist/artifacts/artifacts.js +46 -0
  6. package/dist/benchmark/benchmark.d.ts +23 -0
  7. package/dist/benchmark/benchmark.js +40 -0
  8. package/dist/cli/cli.d.ts +6 -0
  9. package/dist/cli/cli.js +161 -0
  10. package/dist/cli/commands/cleanup.d.ts +47 -0
  11. package/dist/cli/commands/cleanup.js +96 -0
  12. package/dist/cli/commands/config.d.ts +8 -0
  13. package/dist/cli/commands/config.js +28 -0
  14. package/dist/cli/commands/doctor.d.ts +57 -0
  15. package/dist/cli/commands/doctor.js +86 -0
  16. package/dist/cli/commands/executions.d.ts +9 -0
  17. package/dist/cli/commands/executions.js +25 -0
  18. package/dist/cli/commands/info.d.ts +43 -0
  19. package/dist/cli/commands/info.js +53 -0
  20. package/dist/cli/commands/init.d.ts +5 -0
  21. package/dist/cli/commands/init.js +75 -0
  22. package/dist/cli/commands/inspect.d.ts +16 -0
  23. package/dist/cli/commands/inspect.js +60 -0
  24. package/dist/cli/commands/phase2.d.ts +22 -0
  25. package/dist/cli/commands/phase2.js +83 -0
  26. package/dist/cli/commands/route.d.ts +14 -0
  27. package/dist/cli/commands/route.js +49 -0
  28. package/dist/cli/commands/run.d.ts +11 -0
  29. package/dist/cli/commands/run.js +37 -0
  30. package/dist/cli/commands/setup.d.ts +34 -0
  31. package/dist/cli/commands/setup.js +104 -0
  32. package/dist/cli/commands/skills.d.ts +28 -0
  33. package/dist/cli/commands/skills.js +48 -0
  34. package/dist/cli/commands/test.d.ts +7 -0
  35. package/dist/cli/commands/test.js +29 -0
  36. package/dist/cli/context.d.ts +12 -0
  37. package/dist/cli/context.js +16 -0
  38. package/dist/cli/interactive/repl.d.ts +6 -0
  39. package/dist/cli/interactive/repl.js +45 -0
  40. package/dist/cli/interactive/session.d.ts +36 -0
  41. package/dist/cli/interactive/session.js +356 -0
  42. package/dist/cli/prompt.d.ts +6 -0
  43. package/dist/cli/prompt.js +18 -0
  44. package/dist/cli/render.d.ts +7 -0
  45. package/dist/cli/render.js +14 -0
  46. package/dist/comparison/analysis.d.ts +46 -0
  47. package/dist/comparison/analysis.js +177 -0
  48. package/dist/comparison/comparator.d.ts +46 -0
  49. package/dist/comparison/comparator.js +270 -0
  50. package/dist/comparison/comparison.d.ts +140 -0
  51. package/dist/comparison/comparison.js +9 -0
  52. package/dist/comparison/render.d.ts +7 -0
  53. package/dist/comparison/render.js +66 -0
  54. package/dist/config/defaults.d.ts +52 -0
  55. package/dist/config/defaults.js +56 -0
  56. package/dist/config/load.d.ts +17 -0
  57. package/dist/config/load.js +50 -0
  58. package/dist/config/providerDefaults.d.ts +17 -0
  59. package/dist/config/providerDefaults.js +61 -0
  60. package/dist/config/schema.d.ts +9 -0
  61. package/dist/config/schema.js +78 -0
  62. package/dist/context/budget.d.ts +13 -0
  63. package/dist/context/budget.js +17 -0
  64. package/dist/context/compiler.d.ts +61 -0
  65. package/dist/context/compiler.js +125 -0
  66. package/dist/context/tokens.d.ts +19 -0
  67. package/dist/context/tokens.js +38 -0
  68. package/dist/conversations/conversations.d.ts +38 -0
  69. package/dist/conversations/conversations.js +64 -0
  70. package/dist/core/capabilities/evidence.d.ts +40 -0
  71. package/dist/core/capabilities/evidence.js +102 -0
  72. package/dist/core/capabilities/overlay.d.ts +15 -0
  73. package/dist/core/capabilities/overlay.js +0 -0
  74. package/dist/core/capabilities/taxonomy.d.ts +19 -0
  75. package/dist/core/capabilities/taxonomy.js +25 -0
  76. package/dist/core/fallback/errors.d.ts +30 -0
  77. package/dist/core/fallback/errors.js +80 -0
  78. package/dist/core/fallback/fallback.d.ts +40 -0
  79. package/dist/core/fallback/fallback.js +82 -0
  80. package/dist/core/fallback/retryPolicy.d.ts +11 -0
  81. package/dist/core/fallback/retryPolicy.js +14 -0
  82. package/dist/core/health/health.d.ts +3 -0
  83. package/dist/core/health/health.js +5 -0
  84. package/dist/core/health/monitor.d.ts +23 -0
  85. package/dist/core/health/monitor.js +82 -0
  86. package/dist/core/policies/budget.d.ts +19 -0
  87. package/dist/core/policies/budget.js +37 -0
  88. package/dist/core/registry/builtinTasks.d.ts +8 -0
  89. package/dist/core/registry/builtinTasks.js +54 -0
  90. package/dist/core/registry/registry.d.ts +18 -0
  91. package/dist/core/registry/registry.js +33 -0
  92. package/dist/core/registry/taskRegistry.d.ts +15 -0
  93. package/dist/core/registry/taskRegistry.js +30 -0
  94. package/dist/core/router/confidence.d.ts +7 -0
  95. package/dist/core/router/confidence.js +20 -0
  96. package/dist/core/router/dimensions.d.ts +16 -0
  97. package/dist/core/router/dimensions.js +60 -0
  98. package/dist/core/router/executor.d.ts +16 -0
  99. package/dist/core/router/executor.js +25 -0
  100. package/dist/core/router/filter.d.ts +34 -0
  101. package/dist/core/router/filter.js +113 -0
  102. package/dist/core/router/normalize.d.ts +30 -0
  103. package/dist/core/router/normalize.js +119 -0
  104. package/dist/core/router/request.d.ts +4 -0
  105. package/dist/core/router/request.js +21 -0
  106. package/dist/core/router/router.d.ts +32 -0
  107. package/dist/core/router/router.js +195 -0
  108. package/dist/core/router/routingPrefs.d.ts +11 -0
  109. package/dist/core/router/routingPrefs.js +30 -0
  110. package/dist/core/router/scorer.d.ts +19 -0
  111. package/dist/core/router/scorer.js +50 -0
  112. package/dist/core/router/weights.d.ts +9 -0
  113. package/dist/core/router/weights.js +31 -0
  114. package/dist/core/validation/validator.d.ts +16 -0
  115. package/dist/core/validation/validator.js +33 -0
  116. package/dist/discovery/modelCatalog.d.ts +28 -0
  117. package/dist/discovery/modelCatalog.js +105 -0
  118. package/dist/discovery/openapi.d.ts +25 -0
  119. package/dist/discovery/openapi.js +76 -0
  120. package/dist/executions/checkpoint.d.ts +26 -0
  121. package/dist/executions/checkpoint.js +114 -0
  122. package/dist/executions/execution.d.ts +51 -0
  123. package/dist/executions/execution.js +8 -0
  124. package/dist/executions/store.d.ts +52 -0
  125. package/dist/executions/store.js +124 -0
  126. package/dist/generation/generateAdapter.d.ts +17 -0
  127. package/dist/generation/generateAdapter.js +30 -0
  128. package/dist/index.d.ts +147 -0
  129. package/dist/index.js +107 -0
  130. package/dist/learning/feedback.d.ts +9 -0
  131. package/dist/learning/feedback.js +18 -0
  132. package/dist/learning/learningStore.d.ts +68 -0
  133. package/dist/learning/learningStore.js +138 -0
  134. package/dist/learning/performanceStore.d.ts +27 -0
  135. package/dist/learning/performanceStore.js +0 -0
  136. package/dist/marketplace/presets.d.ts +24 -0
  137. package/dist/marketplace/presets.js +52 -0
  138. package/dist/mcp/mcp.d.ts +31 -0
  139. package/dist/mcp/mcp.js +54 -0
  140. package/dist/memory/bm25.d.ts +16 -0
  141. package/dist/memory/bm25.js +56 -0
  142. package/dist/memory/classifier.d.ts +14 -0
  143. package/dist/memory/classifier.js +17 -0
  144. package/dist/memory/memory.d.ts +80 -0
  145. package/dist/memory/memory.js +191 -0
  146. package/dist/orchestration/executor.d.ts +35 -0
  147. package/dist/orchestration/executor.js +65 -0
  148. package/dist/orchestration/orchestrator.d.ts +42 -0
  149. package/dist/orchestration/orchestrator.js +63 -0
  150. package/dist/orchestration/plan.d.ts +37 -0
  151. package/dist/orchestration/plan.js +70 -0
  152. package/dist/orchestration/planner.d.ts +29 -0
  153. package/dist/orchestration/planner.js +69 -0
  154. package/dist/plugin/ai.d.ts +82 -0
  155. package/dist/plugin/ai.js +167 -0
  156. package/dist/probing/probe.d.ts +25 -0
  157. package/dist/probing/probe.js +63 -0
  158. package/dist/providers/factory.d.ts +18 -0
  159. package/dist/providers/factory.js +54 -0
  160. package/dist/providers/httpClient.d.ts +34 -0
  161. package/dist/providers/httpClient.js +80 -0
  162. package/dist/providers/httpProvider.d.ts +49 -0
  163. package/dist/providers/httpProvider.js +135 -0
  164. package/dist/providers/mock/demo.d.ts +13 -0
  165. package/dist/providers/mock/demo.js +58 -0
  166. package/dist/providers/mock/mockProvider.d.ts +35 -0
  167. package/dist/providers/mock/mockProvider.js +121 -0
  168. package/dist/providers/mock/scenarios.d.ts +44 -0
  169. package/dist/providers/mock/scenarios.js +30 -0
  170. package/dist/providers/provider.d.ts +26 -0
  171. package/dist/providers/provider.js +11 -0
  172. package/dist/providers/wire/anthropicWire.d.ts +6 -0
  173. package/dist/providers/wire/anthropicWire.js +83 -0
  174. package/dist/providers/wire/openaiWire.d.ts +7 -0
  175. package/dist/providers/wire/openaiWire.js +81 -0
  176. package/dist/providers/wire/registry.d.ts +8 -0
  177. package/dist/providers/wire/registry.js +20 -0
  178. package/dist/providers/wire/types.d.ts +39 -0
  179. package/dist/providers/wire/types.js +24 -0
  180. package/dist/runtime/config.d.ts +31 -0
  181. package/dist/runtime/config.js +121 -0
  182. package/dist/runtime/context.d.ts +34 -0
  183. package/dist/runtime/context.js +11 -0
  184. package/dist/runtime/events.d.ts +99 -0
  185. package/dist/runtime/events.js +82 -0
  186. package/dist/runtime/host.d.ts +27 -0
  187. package/dist/runtime/host.js +7 -0
  188. package/dist/runtime/intent/classifier.d.ts +30 -0
  189. package/dist/runtime/intent/classifier.js +60 -0
  190. package/dist/runtime/intent/signals.d.ts +19 -0
  191. package/dist/runtime/intent/signals.js +46 -0
  192. package/dist/runtime/modes/availability.d.ts +11 -0
  193. package/dist/runtime/modes/availability.js +17 -0
  194. package/dist/runtime/modes/chat.d.ts +18 -0
  195. package/dist/runtime/modes/chat.js +67 -0
  196. package/dist/runtime/modes/modeResolver.d.ts +43 -0
  197. package/dist/runtime/modes/modeResolver.js +78 -0
  198. package/dist/runtime/policy.d.ts +72 -0
  199. package/dist/runtime/policy.js +59 -0
  200. package/dist/runtime/providerView.d.ts +62 -0
  201. package/dist/runtime/providerView.js +105 -0
  202. package/dist/runtime/routing.d.ts +26 -0
  203. package/dist/runtime/routing.js +65 -0
  204. package/dist/runtime/runtime.d.ts +191 -0
  205. package/dist/runtime/runtime.js +718 -0
  206. package/dist/runtime/types.d.ts +153 -0
  207. package/dist/runtime/types.js +9 -0
  208. package/dist/runtime/workspace/detectors.d.ts +15 -0
  209. package/dist/runtime/workspace/detectors.js +57 -0
  210. package/dist/runtime/workspace/workspace.d.ts +29 -0
  211. package/dist/runtime/workspace/workspace.js +116 -0
  212. package/dist/security/credentials.d.ts +26 -0
  213. package/dist/security/credentials.js +34 -0
  214. package/dist/security/redact.d.ts +16 -0
  215. package/dist/security/redact.js +57 -0
  216. package/dist/skills/builtins/fileAnalyzer.d.ts +7 -0
  217. package/dist/skills/builtins/fileAnalyzer.js +47 -0
  218. package/dist/skills/builtins/repositoryAnalyzer.d.ts +6 -0
  219. package/dist/skills/builtins/repositoryAnalyzer.js +47 -0
  220. package/dist/skills/discovery.d.ts +61 -0
  221. package/dist/skills/discovery.js +211 -0
  222. package/dist/skills/manifest.d.ts +30 -0
  223. package/dist/skills/manifest.js +75 -0
  224. package/dist/skills/registry.d.ts +15 -0
  225. package/dist/skills/registry.js +22 -0
  226. package/dist/skills/skill.d.ts +64 -0
  227. package/dist/skills/skill.js +8 -0
  228. package/dist/store/area.d.ts +54 -0
  229. package/dist/store/area.js +164 -0
  230. package/dist/store/paths.d.ts +15 -0
  231. package/dist/store/paths.js +48 -0
  232. package/dist/store/store.d.ts +59 -0
  233. package/dist/store/store.js +140 -0
  234. package/dist/telemetry/sinks/file.d.ts +12 -0
  235. package/dist/telemetry/sinks/file.js +28 -0
  236. package/dist/telemetry/telemetry.d.ts +36 -0
  237. package/dist/telemetry/telemetry.js +63 -0
  238. package/dist/tools/builtins/filesystem.d.ts +7 -0
  239. package/dist/tools/builtins/filesystem.js +53 -0
  240. package/dist/tools/builtins/git.d.ts +10 -0
  241. package/dist/tools/builtins/git.js +66 -0
  242. package/dist/tools/builtins/shell.d.ts +17 -0
  243. package/dist/tools/builtins/shell.js +91 -0
  244. package/dist/tools/jail.d.ts +12 -0
  245. package/dist/tools/jail.js +98 -0
  246. package/dist/tools/permissions.d.ts +25 -0
  247. package/dist/tools/permissions.js +24 -0
  248. package/dist/tools/registry.d.ts +10 -0
  249. package/dist/tools/registry.js +20 -0
  250. package/dist/tools/runner.d.ts +23 -0
  251. package/dist/tools/runner.js +64 -0
  252. package/dist/tools/tool.d.ts +53 -0
  253. package/dist/tools/tool.js +24 -0
  254. package/dist/tools/untrusted.d.ts +13 -0
  255. package/dist/tools/untrusted.js +30 -0
  256. package/dist/types.d.ts +460 -0
  257. package/dist/types.js +12 -0
  258. package/dist/util/clock.d.ts +6 -0
  259. package/dist/util/clock.js +4 -0
  260. package/dist/util/extractJson.d.ts +8 -0
  261. package/dist/util/extractJson.js +54 -0
  262. package/dist/verification/verify.d.ts +26 -0
  263. package/dist/verification/verify.js +67 -0
  264. package/docs/GUIDE.md +358 -0
  265. package/docs/README.md +21 -0
  266. package/docs/architecture.md +78 -0
  267. package/docs/router.md +376 -0
  268. package/docs/security.md +55 -0
  269. package/package.json +67 -0
@@ -0,0 +1,54 @@
1
+ /**
2
+ * MCP integration. MCP is treated as an INTEGRATION LAYER for tools/context, NOT as another
3
+ * model provider. A source exposes tools (as normalized
4
+ * ToolSpecs) that the router hands to whichever model it selects; the model's tool_calling capability
5
+ * is what carries them. Sources are pluggable; a StaticMcpSource covers in-process tools and tests.
6
+ *
7
+ * This is a minimal, transport-agnostic surface: real stdio/HTTP MCP clients implement `McpToolSource`.
8
+ */
9
+ export class StaticMcpSource {
10
+ id;
11
+ tools;
12
+ handlers;
13
+ constructor(id, tools, handlers = {}) {
14
+ this.id = id;
15
+ this.tools = tools;
16
+ this.handlers = handlers;
17
+ }
18
+ async listTools() {
19
+ return this.tools;
20
+ }
21
+ async callTool(name, args) {
22
+ const handler = this.handlers[name];
23
+ if (!handler)
24
+ throw new Error(`MCP source '${this.id}' has no handler for tool '${name}'`);
25
+ return handler(args);
26
+ }
27
+ }
28
+ export class McpRegistry {
29
+ sources = new Map();
30
+ register(source) {
31
+ this.sources.set(source.id, source);
32
+ }
33
+ list() {
34
+ return [...this.sources.values()];
35
+ }
36
+ /** Aggregate tools across all registered sources. Tool names are namespaced by source to avoid clashes. */
37
+ async allTools() {
38
+ const out = [];
39
+ for (const source of this.sources.values()) {
40
+ for (const tool of await source.listTools()) {
41
+ out.push({ ...tool, name: tool.name.includes('.') ? tool.name : `${source.id}.${tool.name}` });
42
+ }
43
+ }
44
+ return out;
45
+ }
46
+ /** Route a (namespaced) tool call to its source. */
47
+ async call(name, args) {
48
+ const [sourceId, ...rest] = name.split('.');
49
+ const source = sourceId ? this.sources.get(sourceId) : undefined;
50
+ if (!source || !source.callTool)
51
+ throw new Error(`no MCP source can handle tool '${name}'`);
52
+ return source.callTool(rest.join('.') || name, args);
53
+ }
54
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * A compact, dependency-free BM25 ranker — the default memory retrieval scorer. It runs offline and
3
+ * deterministically. An `EmbeddingProvider` seam (memory/memory.ts) lets real embeddings replace this
4
+ * later without changing the retrieval API.
5
+ */
6
+ export declare function tokenize(text: string): string[];
7
+ export interface Bm25Doc {
8
+ id: string;
9
+ text: string;
10
+ }
11
+ export interface Bm25Hit {
12
+ id: string;
13
+ score: number;
14
+ }
15
+ /** Rank docs against a query with BM25. Returns hits with score > 0, highest first. */
16
+ export declare function bm25Search(query: string, docs: Bm25Doc[], limit?: number): Bm25Hit[];
@@ -0,0 +1,56 @@
1
+ /**
2
+ * A compact, dependency-free BM25 ranker — the default memory retrieval scorer. It runs offline and
3
+ * deterministically. An `EmbeddingProvider` seam (memory/memory.ts) lets real embeddings replace this
4
+ * later without changing the retrieval API.
5
+ */
6
+ const STOPWORDS = new Set([
7
+ 'the', 'a', 'an', 'and', 'or', 'but', 'is', 'are', 'was', 'were', 'be', 'been', 'to', 'of', 'in', 'on', 'for', 'with', 'as', 'by', 'at', 'this', 'that', 'these', 'those', 'it', 'its', 'i', 'you', 'we', 'they', 'do', 'does', 'did', 'has', 'have', 'had', 'will', 'would', 'should', 'can', 'could',
8
+ ]);
9
+ export function tokenize(text) {
10
+ const out = [];
11
+ for (const m of text.toLowerCase().matchAll(/[a-z0-9]+/g)) {
12
+ const tok = m[0];
13
+ if (tok.length >= 2 && !STOPWORDS.has(tok))
14
+ out.push(tok);
15
+ }
16
+ return out;
17
+ }
18
+ const K1 = 1.5;
19
+ const B = 0.75;
20
+ /** Rank docs against a query with BM25. Returns hits with score > 0, highest first. */
21
+ export function bm25Search(query, docs, limit = 10) {
22
+ const queryTerms = [...new Set(tokenize(query))];
23
+ if (queryTerms.length === 0 || docs.length === 0)
24
+ return [];
25
+ const tokenized = docs.map((d) => ({ id: d.id, terms: tokenize(d.text) }));
26
+ const N = tokenized.length;
27
+ const avgdl = tokenized.reduce((s, d) => s + d.terms.length, 0) / N || 1;
28
+ const df = new Map();
29
+ for (const term of queryTerms) {
30
+ let count = 0;
31
+ for (const d of tokenized)
32
+ if (d.terms.includes(term))
33
+ count += 1;
34
+ df.set(term, count);
35
+ }
36
+ const hits = [];
37
+ for (const d of tokenized) {
38
+ const dl = d.terms.length;
39
+ const tf = new Map();
40
+ for (const t of d.terms)
41
+ tf.set(t, (tf.get(t) ?? 0) + 1);
42
+ let score = 0;
43
+ for (const term of queryTerms) {
44
+ const f = tf.get(term);
45
+ if (!f)
46
+ continue;
47
+ const n = df.get(term);
48
+ const idf = Math.log(1 + (N - n + 0.5) / (n + 0.5));
49
+ score += idf * ((f * (K1 + 1)) / (f + K1 * (1 - B + (B * dl) / avgdl)));
50
+ }
51
+ if (score > 0)
52
+ hits.push({ id: d.id, score });
53
+ }
54
+ hits.sort((a, b) => b.score - a.score || (a.id < b.id ? -1 : 1));
55
+ return hits.slice(0, limit);
56
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Explicit-memory classifier. Detects a clear "remember this" instruction in user text and extracts the
3
+ * fact + a scope guess. Conservative by design: only explicit requests become high-confidence memories
4
+ * here. Observed/inferred capture (from execution outcomes) arrives in later phases at low confidence.
5
+ */
6
+ import type { MemoryScope, MemorySource } from './memory.js';
7
+ export interface MemoryCandidate {
8
+ text: string;
9
+ scope: MemoryScope;
10
+ source: MemorySource;
11
+ confidence: number;
12
+ }
13
+ /** Returns a memory candidate for an explicit "remember …" instruction, else undefined. */
14
+ export declare function classifyMemory(input: string): MemoryCandidate | undefined;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Explicit-memory classifier. Detects a clear "remember this" instruction in user text and extracts the
3
+ * fact + a scope guess. Conservative by design: only explicit requests become high-confidence memories
4
+ * here. Observed/inferred capture (from execution outcomes) arrives in later phases at low confidence.
5
+ */
6
+ const TRIGGER = /^\s*(?:please\s+)?(?:remember|note|keep in mind|don'?t forget|do not forget)(?:\s+that)?\s*[:,-]?\s+(.+)$/i;
7
+ /** Returns a memory candidate for an explicit "remember …" instruction, else undefined. */
8
+ export function classifyMemory(input) {
9
+ const m = TRIGGER.exec(input);
10
+ if (!m)
11
+ return undefined;
12
+ const fact = m[1].trim().replace(/\s+/g, ' ');
13
+ if (!fact)
14
+ return undefined;
15
+ const scope = /\b(i|my|me)\b.*\b(prefer|like|want|always|never|usually|hate|favou?rite)\b/i.test(fact) || /^my\b/i.test(fact) ? 'user' : 'project';
16
+ return { text: fact, scope, source: 'explicit', confidence: 0.9 };
17
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Memory store — durable, scoped facts (distinct from conversations and learning). Records are redacted
3
+ * before storage, ranked at query time with BM25 (+ metadata/temporal/relationship filtering), and
4
+ * deleted with a full cascade + audit. Conflict resolution supersedes contradictory facts rather than
5
+ * blindly merging. Physical placement: user → shared user store; repository → repo-shared; everything
6
+ * else → the per-project store (so monorepo packages don't cross-contaminate).
7
+ */
8
+ import type { Clock } from '../util/clock.js';
9
+ import type { RuntimeStore } from '../store/store.js';
10
+ export type MemoryScope = 'user' | 'project' | 'repository' | 'domain' | 'skill' | 'execution';
11
+ export type MemorySource = 'explicit' | 'observed' | 'inferred' | 'imported';
12
+ export interface MemoryRecord {
13
+ id: string;
14
+ scope: MemoryScope;
15
+ text: string;
16
+ domain?: string;
17
+ source: MemorySource;
18
+ confidence: number;
19
+ createdAt: number;
20
+ updatedAt: number;
21
+ validUntil?: number;
22
+ supersedes?: string[];
23
+ supersededBy?: string;
24
+ conflictGroup?: string;
25
+ sourcePriority?: number;
26
+ relatedIds?: string[];
27
+ }
28
+ export interface RememberInput {
29
+ text: string;
30
+ scope?: MemoryScope;
31
+ source?: MemorySource;
32
+ confidence?: number;
33
+ domain?: string;
34
+ validUntil?: number;
35
+ conflictGroup?: string;
36
+ sourcePriority?: number;
37
+ relatedIds?: string[];
38
+ }
39
+ export interface SearchOptions {
40
+ scopes?: MemoryScope[];
41
+ limit?: number;
42
+ minConfidence?: number;
43
+ domain?: string;
44
+ now?: number;
45
+ }
46
+ export interface DeletionAudit {
47
+ id: string;
48
+ primary: boolean;
49
+ indexes: boolean;
50
+ relationships: number;
51
+ cache: boolean;
52
+ }
53
+ /** Future extension seam: real embeddings replace BM25 without changing the retrieval API. */
54
+ export interface EmbeddingProvider {
55
+ embed(texts: string[]): Promise<number[][]>;
56
+ }
57
+ export declare class MemoryStore {
58
+ private readonly store;
59
+ private readonly clock;
60
+ private counter;
61
+ constructor(store: RuntimeStore, clock?: Clock);
62
+ get enabled(): boolean;
63
+ private area;
64
+ private nextId;
65
+ /** Store a fact. Redacts text, resolves conflicts (supersedes prior same-group facts). */
66
+ remember(input: RememberInput): MemoryRecord;
67
+ /** Mark existing records in the same conflict group as superseded by the new record. */
68
+ private supersedeConflicts;
69
+ get(id: string): MemoryRecord | undefined;
70
+ /** Every stored record across scopes (including superseded/expired). A corrupt file is skipped. */
71
+ all(): MemoryRecord[];
72
+ /** Retrieval: filter (scope/domain/confidence/temporal/superseded) → BM25 rank → top-N. */
73
+ search(query: string, opts?: SearchOptions): MemoryRecord[];
74
+ /** Delete a record and cascade (strip references, heal supersession chains), under the lock. */
75
+ delete(id: string): DeletionAudit;
76
+ /** The cascade itself (assumes the caller holds the lock). Heals A→B→C when B is removed. */
77
+ private removeCascade;
78
+ /** Remove expired records with full cascade, under the lock. Returns removed ids. */
79
+ purgeExpired(now?: number): string[];
80
+ }
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Memory store — durable, scoped facts (distinct from conversations and learning). Records are redacted
3
+ * before storage, ranked at query time with BM25 (+ metadata/temporal/relationship filtering), and
4
+ * deleted with a full cascade + audit. Conflict resolution supersedes contradictory facts rather than
5
+ * blindly merging. Physical placement: user → shared user store; repository → repo-shared; everything
6
+ * else → the per-project store (so monorepo packages don't cross-contaminate).
7
+ */
8
+ import { systemClock } from '../util/clock.js';
9
+ import { redactString } from '../security/redact.js';
10
+ import { bm25Search } from './bm25.js';
11
+ function physical(scope) {
12
+ if (scope === 'user')
13
+ return 'user';
14
+ if (scope === 'repository')
15
+ return 'repository';
16
+ return 'project';
17
+ }
18
+ const PHYSICAL = ['user', 'project', 'repository'];
19
+ export class MemoryStore {
20
+ store;
21
+ clock;
22
+ counter = 0;
23
+ constructor(store, clock = systemClock) {
24
+ this.store = store;
25
+ this.clock = clock;
26
+ }
27
+ get enabled() {
28
+ return this.store.enabled;
29
+ }
30
+ area(scope) {
31
+ return this.store.memory(physical(scope));
32
+ }
33
+ nextId() {
34
+ // clock + pid + per-instance counter + entropy → collision-safe across instances and processes.
35
+ return `mem_${this.clock.now().toString(36)}_${process.pid.toString(36)}_${(this.counter += 1).toString(36)}_${Math.random().toString(36).slice(2, 6)}`;
36
+ }
37
+ /** Store a fact. Redacts text, resolves conflicts (supersedes prior same-group facts). */
38
+ remember(input) {
39
+ const now = this.clock.now();
40
+ const scope = input.scope ?? 'project';
41
+ const record = {
42
+ id: this.nextId(),
43
+ scope,
44
+ text: redactString(input.text),
45
+ source: input.source ?? 'explicit',
46
+ confidence: clamp(input.confidence ?? 0.9),
47
+ createdAt: now,
48
+ updatedAt: now,
49
+ ...(input.domain ? { domain: input.domain } : {}),
50
+ ...(input.validUntil !== undefined ? { validUntil: input.validUntil } : {}),
51
+ ...(input.conflictGroup ? { conflictGroup: input.conflictGroup } : {}),
52
+ ...(input.sourcePriority !== undefined ? { sourcePriority: input.sourcePriority } : {}),
53
+ ...(input.relatedIds ? { relatedIds: input.relatedIds } : {}),
54
+ };
55
+ this.store.withLock(() => {
56
+ if (record.conflictGroup)
57
+ this.supersedeConflicts(record);
58
+ this.area(scope).writeJson(record.id, record);
59
+ });
60
+ return record;
61
+ }
62
+ /** Mark existing records in the same conflict group as superseded by the new record. */
63
+ supersedeConflicts(incoming) {
64
+ const superseded = [];
65
+ for (const scope of PHYSICAL) {
66
+ const area = this.store.memory(scope);
67
+ for (const id of area.listIds()) {
68
+ const rec = area.tryReadJson(id);
69
+ if (!rec || rec.id === incoming.id || rec.supersededBy)
70
+ continue;
71
+ if (rec.conflictGroup && rec.conflictGroup === incoming.conflictGroup) {
72
+ rec.supersededBy = incoming.id;
73
+ rec.updatedAt = incoming.createdAt;
74
+ area.writeJson(rec.id, rec);
75
+ superseded.push(rec.id);
76
+ }
77
+ }
78
+ }
79
+ if (superseded.length)
80
+ incoming.supersedes = superseded;
81
+ }
82
+ get(id) {
83
+ for (const scope of PHYSICAL) {
84
+ const rec = this.store.memory(scope).tryReadJson(id);
85
+ if (rec)
86
+ return rec;
87
+ }
88
+ return undefined;
89
+ }
90
+ /** Every stored record across scopes (including superseded/expired). A corrupt file is skipped. */
91
+ all() {
92
+ const out = [];
93
+ for (const scope of PHYSICAL) {
94
+ const area = this.store.memory(scope);
95
+ for (const id of area.listIds()) {
96
+ const rec = area.tryReadJson(id);
97
+ if (rec)
98
+ out.push(rec);
99
+ }
100
+ }
101
+ return out;
102
+ }
103
+ /** Retrieval: filter (scope/domain/confidence/temporal/superseded) → BM25 rank → top-N. */
104
+ search(query, opts = {}) {
105
+ const now = opts.now ?? this.clock.now();
106
+ const minConfidence = opts.minConfidence ?? 0;
107
+ const scopeFilter = opts.scopes ? new Set(opts.scopes) : undefined;
108
+ const all = this.all();
109
+ const byId = new Map(all.map((r) => [r.id, r]));
110
+ // A record is hidden only when its superseder still EXISTS and is not itself expired — so a fact
111
+ // superseded by a temporary record becomes live again once that record expires or is deleted.
112
+ const supersederActive = (sid) => {
113
+ const s = byId.get(sid);
114
+ return !!s && (s.validUntil === undefined || s.validUntil >= now);
115
+ };
116
+ const candidates = all.filter((r) => {
117
+ if (r.validUntil !== undefined && r.validUntil < now)
118
+ return false;
119
+ if (r.supersededBy && supersederActive(r.supersededBy))
120
+ return false;
121
+ if (r.confidence < minConfidence)
122
+ return false;
123
+ if (scopeFilter && !scopeFilter.has(r.scope))
124
+ return false;
125
+ if (opts.domain && r.domain !== opts.domain)
126
+ return false;
127
+ return true;
128
+ });
129
+ const candById = new Map(candidates.map((r) => [r.id, r]));
130
+ const hits = bm25Search(query, candidates.map((r) => ({ id: r.id, text: r.text })), opts.limit ?? 10);
131
+ return hits.map((h) => candById.get(h.id)).filter((r) => r !== undefined);
132
+ }
133
+ /** Delete a record and cascade (strip references, heal supersession chains), under the lock. */
134
+ delete(id) {
135
+ return this.store.withLock(() => this.removeCascade(id));
136
+ }
137
+ /** The cascade itself (assumes the caller holds the lock). Heals A→B→C when B is removed. */
138
+ removeCascade(id) {
139
+ const target = this.get(id);
140
+ const inheritor = target?.supersededBy; // if the removed record was itself superseded, heirs re-link to its head
141
+ let primary = false;
142
+ let relationships = 0;
143
+ for (const scope of PHYSICAL) {
144
+ const area = this.store.memory(scope);
145
+ for (const other of area.listIds()) {
146
+ if (other === id) {
147
+ area.remove(id);
148
+ primary = true;
149
+ continue;
150
+ }
151
+ const rec = area.tryReadJson(other);
152
+ if (!rec)
153
+ continue;
154
+ let changed = false;
155
+ if (rec.relatedIds?.includes(id)) {
156
+ rec.relatedIds = rec.relatedIds.filter((r) => r !== id);
157
+ changed = true;
158
+ }
159
+ if (rec.supersededBy === id) {
160
+ if (inheritor)
161
+ rec.supersededBy = inheritor; // chain heals: still superseded by the newer head
162
+ else
163
+ delete rec.supersededBy; // the removed record was the head → this one is live again
164
+ changed = true;
165
+ }
166
+ if (rec.supersedes?.includes(id)) {
167
+ rec.supersedes = rec.supersedes.filter((r) => r !== id);
168
+ changed = true;
169
+ }
170
+ if (changed) {
171
+ relationships += 1;
172
+ area.writeJson(rec.id, rec);
173
+ }
174
+ }
175
+ }
176
+ // Indexes are derived from records at query time; nothing separate to purge (true once persisted).
177
+ return { id, primary, indexes: true, relationships, cache: true };
178
+ }
179
+ /** Remove expired records with full cascade, under the lock. Returns removed ids. */
180
+ purgeExpired(now = this.clock.now()) {
181
+ return this.store.withLock(() => {
182
+ const expired = this.all().filter((r) => r.validUntil !== undefined && r.validUntil < now).map((r) => r.id);
183
+ for (const id of expired)
184
+ this.removeCascade(id);
185
+ return expired;
186
+ });
187
+ }
188
+ }
189
+ function clamp(n) {
190
+ return Math.max(0, Math.min(1, n));
191
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Executor — runs a validated plan's steps through the runtime's skills/tools, DAG-ordered, up to
3
+ * `maxParallelSteps` at a time. A step succeeds only on EVIDENCE (a skill's own validation, or a tool's
4
+ * ok result) — never a model's say-so. A step whose dependency failed is skipped, not run. The executor
5
+ * never mutates anything itself; all side effects go through the permission-gated tools.
6
+ */
7
+ import type { SkillResult, SkillValidation } from '../skills/skill.js';
8
+ import type { ToolResult } from '../tools/tool.js';
9
+ import type { ExecutionPlan } from './plan.js';
10
+ export interface StepObservation {
11
+ stepId: string;
12
+ skill?: string;
13
+ tool?: string;
14
+ ok: boolean;
15
+ output?: string;
16
+ error?: string;
17
+ }
18
+ export interface ExecuteResult {
19
+ ok: boolean;
20
+ plan: ExecutionPlan;
21
+ observations: StepObservation[];
22
+ }
23
+ export interface ExecuteDeps {
24
+ runSkill: (id: string, input: unknown) => Promise<{
25
+ result: SkillResult;
26
+ validation: SkillValidation;
27
+ }>;
28
+ runTool: (id: string, input: unknown) => Promise<ToolResult>;
29
+ maxParallelSteps?: number;
30
+ signal?: AbortSignal;
31
+ /** Step ids already completed in a prior run — marked succeeded and NOT re-executed (resume). */
32
+ skip?: ReadonlySet<string>;
33
+ }
34
+ /** Execute a plan. Runs DAG waves; within a wave, batches of at most maxParallelSteps run concurrently. */
35
+ export declare function executePlan(plan: ExecutionPlan, deps: ExecuteDeps): Promise<ExecuteResult>;
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Executor — runs a validated plan's steps through the runtime's skills/tools, DAG-ordered, up to
3
+ * `maxParallelSteps` at a time. A step succeeds only on EVIDENCE (a skill's own validation, or a tool's
4
+ * ok result) — never a model's say-so. A step whose dependency failed is skipped, not run. The executor
5
+ * never mutates anything itself; all side effects go through the permission-gated tools.
6
+ */
7
+ import { executionWaves } from './plan.js';
8
+ async function runStep(step, deps) {
9
+ try {
10
+ if (step.skill) {
11
+ const { result, validation } = await deps.runSkill(step.skill, step.input);
12
+ const ok = result.ok && validation.ok;
13
+ return { stepId: step.id, skill: step.skill, ok, ...(result.output !== undefined ? { output: result.output } : {}), ...(ok ? {} : { error: result.error?.message ?? validation.checks.find((c) => !c.ok)?.why ?? 'validation failed' }) };
14
+ }
15
+ if (step.tool) {
16
+ const result = await deps.runTool(step.tool, step.input);
17
+ return { stepId: step.id, tool: step.tool, ok: result.ok, ...(result.output !== undefined ? { output: result.output } : {}), ...(result.ok ? {} : { error: result.error?.message ?? 'tool failed' }) };
18
+ }
19
+ return { stepId: step.id, ok: false, error: 'step names neither a skill nor a tool' };
20
+ }
21
+ catch (err) {
22
+ return { stepId: step.id, ok: false, error: err instanceof Error ? err.message : String(err) };
23
+ }
24
+ }
25
+ /** Execute a plan. Runs DAG waves; within a wave, batches of at most maxParallelSteps run concurrently. */
26
+ export async function executePlan(plan, deps) {
27
+ const max = Math.max(1, deps.maxParallelSteps ?? 2);
28
+ const observations = [];
29
+ const failedOrSkipped = new Set(); // read for dependency-skipping; success is tracked via step.status
30
+ for (const wave of executionWaves(plan.steps)) {
31
+ // A step runs only if all its dependencies succeeded; otherwise it is skipped.
32
+ const runnable = [];
33
+ for (const step of wave) {
34
+ const depsFailed = (step.dependsOn ?? []).some((d) => failedOrSkipped.has(d));
35
+ if (deps.skip?.has(step.id)) {
36
+ step.status = 'succeeded'; // already done in a prior run (resume) — not re-executed
37
+ }
38
+ else if (deps.signal?.aborted) {
39
+ step.status = 'skipped';
40
+ failedOrSkipped.add(step.id);
41
+ }
42
+ else if (depsFailed) {
43
+ step.status = 'skipped';
44
+ failedOrSkipped.add(step.id);
45
+ observations.push({ stepId: step.id, ok: false, error: 'skipped: a dependency failed' });
46
+ }
47
+ else {
48
+ runnable.push(step);
49
+ }
50
+ }
51
+ for (let i = 0; i < runnable.length; i += max) {
52
+ const batch = runnable.slice(i, i + max);
53
+ batch.forEach((s) => (s.status = 'running'));
54
+ const results = await Promise.all(batch.map((s) => runStep(s, deps)));
55
+ results.forEach((obs, j) => {
56
+ const step = batch[j];
57
+ step.status = obs.ok ? 'succeeded' : 'failed';
58
+ if (!obs.ok)
59
+ failedOrSkipped.add(step.id);
60
+ observations.push(obs);
61
+ });
62
+ }
63
+ }
64
+ return { ok: plan.steps.every((s) => s.status === 'succeeded'), plan, observations };
65
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Orchestrator — the understand→plan→(approve/dry-run)→execute→observe→validate→done?/replan loop.
3
+ * The model proposes plans; the Runtime owns execution, approval, and limits (from ExecutionPolicy).
4
+ * `plan` mode plans only; `execute` runs a single planned pass; `orchestrate` replans on failure up to
5
+ * the iteration limit. Dry-run performs ZERO mutations. Missing information surfaces as clarification.
6
+ */
7
+ import type { AI } from '../plugin/ai.js';
8
+ import type { RoutingPreferences } from '../types.js';
9
+ import type { Skill, SkillResult, SkillValidation } from '../skills/skill.js';
10
+ import type { ApprovalProvider } from '../runtime/host.js';
11
+ import type { ExecutionPolicy } from '../runtime/policy.js';
12
+ import type { ToolResult } from '../tools/tool.js';
13
+ import type { StepObservation } from './executor.js';
14
+ import type { ExecutionPlan } from './plan.js';
15
+ export type OrchestrationStatus = 'completed' | 'failed' | 'waiting_for_clarification' | 'waiting_for_approval' | 'dry-run';
16
+ export interface OrchestrateInput {
17
+ goal: string;
18
+ /** plan = plan only; execute = single pass; orchestrate/agent/debug = replan loop. */
19
+ mode: 'plan' | 'execute' | 'orchestrate' | 'agent' | 'debug';
20
+ ai: AI;
21
+ skills: Skill[];
22
+ tools: string[];
23
+ policy: ExecutionPolicy;
24
+ approval?: ApprovalProvider;
25
+ runSkill: (id: string, input: unknown) => Promise<{
26
+ result: SkillResult;
27
+ validation: SkillValidation;
28
+ }>;
29
+ runTool: (id: string, input: unknown) => Promise<ToolResult>;
30
+ signal?: AbortSignal;
31
+ /** User exclude/prefer routing, applied to planning-model calls. */
32
+ routing?: RoutingPreferences;
33
+ }
34
+ export interface OrchestrateOutcome {
35
+ status: OrchestrationStatus;
36
+ plan?: ExecutionPlan;
37
+ planHistory: ExecutionPlan[];
38
+ observations: StepObservation[];
39
+ clarification?: string;
40
+ summary: string;
41
+ }
42
+ export declare function orchestrate(input: OrchestrateInput): Promise<OrchestrateOutcome>;
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Orchestrator — the understand→plan→(approve/dry-run)→execute→observe→validate→done?/replan loop.
3
+ * The model proposes plans; the Runtime owns execution, approval, and limits (from ExecutionPolicy).
4
+ * `plan` mode plans only; `execute` runs a single planned pass; `orchestrate` replans on failure up to
5
+ * the iteration limit. Dry-run performs ZERO mutations. Missing information surfaces as clarification.
6
+ */
7
+ import { generatePlan } from './planner.js';
8
+ import { executePlan } from './executor.js';
9
+ function planSummary(plan) {
10
+ const lines = plan.steps.map((s) => ` ${s.id}. ${s.description} [${s.skill ? 'skill:' + s.skill : 'tool:' + s.tool}]`);
11
+ return `Plan v${plan.version} for "${plan.goal}" (${plan.steps.length} steps):\n${lines.join('\n')}`;
12
+ }
13
+ export async function orchestrate(input) {
14
+ const planHistory = [];
15
+ const loops = input.mode === 'orchestrate' || input.mode === 'agent' || input.mode === 'debug';
16
+ const maxIterations = loops ? Math.max(1, input.policy.maxIterations ?? (input.mode === 'agent' ? 10 : 5)) : 1;
17
+ const needsApproval = input.policy.approval !== 'none' && input.policy.autonomy !== 'chat';
18
+ let priorObservations = [];
19
+ // Accumulate observations across replan iterations so an early return never discards the trace of
20
+ // prior iterations that already executed (and may have mutated the workspace).
21
+ const allObservations = [];
22
+ for (let version = 1; version <= maxIterations; version += 1) {
23
+ const planResult = await generatePlan({
24
+ goal: input.goal,
25
+ ai: input.ai,
26
+ skills: input.skills,
27
+ tools: input.tools,
28
+ version,
29
+ ...(input.routing ? { routing: input.routing } : {}),
30
+ ...(version > 1 ? { reason: 'previous attempt did not complete', priorObservations } : {}),
31
+ });
32
+ if (planResult.clarification)
33
+ return { status: 'waiting_for_clarification', planHistory, observations: [...allObservations], clarification: planResult.clarification, summary: planResult.clarification };
34
+ if (!planResult.plan)
35
+ return { status: 'failed', planHistory, observations: [...allObservations], summary: planResult.error ?? 'planning failed' };
36
+ const plan = planResult.plan;
37
+ planHistory.push(plan);
38
+ if (input.mode === 'plan')
39
+ return { status: 'completed', plan, planHistory, observations: [...allObservations], summary: `${planSummary(plan)}\n(plan only — not executed)` };
40
+ // Dry-run: report, never execute. Checked BEFORE approval — a dry run changes nothing, so it never
41
+ // needs approval.
42
+ if (input.policy.dryRun)
43
+ return { status: 'dry-run', plan, planHistory, observations: [...allObservations], summary: `${planSummary(plan)}\n(dry run — no changes were made)` };
44
+ // Approval gate (assisted/autonomous with an approval level).
45
+ if (needsApproval) {
46
+ if (!input.approval)
47
+ return { status: 'waiting_for_approval', plan, planHistory, observations: [...allObservations], summary: `${planSummary(plan)}\nApproval required before execution.` };
48
+ const approved = await input.approval.requestApproval({ action: `execute plan v${plan.version} for "${plan.goal}"`, reason: planSummary(plan), risk: 'medium' });
49
+ if (!approved)
50
+ return { status: 'failed', plan, planHistory, observations: [...allObservations], summary: 'plan was not approved' };
51
+ }
52
+ const exec = await executePlan(plan, { runSkill: input.runSkill, runTool: input.runTool, ...(input.policy.maxParallelSteps ? { maxParallelSteps: input.policy.maxParallelSteps } : {}), ...(input.signal ? { signal: input.signal } : {}) });
53
+ allObservations.push(...exec.observations);
54
+ if (exec.ok)
55
+ return { status: 'completed', plan, planHistory, observations: [...allObservations], summary: `completed "${plan.goal}" in ${plan.steps.length} step(s)` };
56
+ // Failed: gather evidence and replan (orchestrate only) or stop.
57
+ priorObservations = exec.observations.filter((o) => !o.ok).map((o) => `${o.stepId}: ${o.error ?? 'failed'}`);
58
+ if (!loops || version === maxIterations) {
59
+ return { status: 'failed', plan, planHistory, observations: [...allObservations], summary: `did not complete "${plan.goal}" after ${version} attempt(s): ${priorObservations.join('; ')}` };
60
+ }
61
+ }
62
+ return { status: 'failed', planHistory, observations: [...allObservations], summary: 'exhausted planning attempts' };
63
+ }