open-multi-agent-kit 0.95.1 → 0.96.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 (306) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +16 -1
  3. package/dist/approvals/approval-receipt-store.d.ts +18 -0
  4. package/dist/approvals/approval-receipt-store.d.ts.map +1 -0
  5. package/dist/approvals/approval-receipt-store.js +194 -0
  6. package/dist/approvals/approval-receipt-store.js.map +1 -0
  7. package/dist/approvals/approval-receipt.d.ts +43 -0
  8. package/dist/approvals/approval-receipt.d.ts.map +1 -0
  9. package/dist/approvals/approval-receipt.js +208 -0
  10. package/dist/approvals/approval-receipt.js.map +1 -0
  11. package/dist/approvals/approval-verifier.d.ts +17 -0
  12. package/dist/approvals/approval-verifier.d.ts.map +1 -0
  13. package/dist/approvals/approval-verifier.js +23 -0
  14. package/dist/approvals/approval-verifier.js.map +1 -0
  15. package/dist/approvals/plannotator-approval-bridge.d.ts +58 -0
  16. package/dist/approvals/plannotator-approval-bridge.d.ts.map +1 -0
  17. package/dist/approvals/plannotator-approval-bridge.js +169 -0
  18. package/dist/approvals/plannotator-approval-bridge.js.map +1 -0
  19. package/dist/cli/args.d.ts.map +1 -1
  20. package/dist/cli/args.js +2 -0
  21. package/dist/cli/args.js.map +1 -1
  22. package/dist/commands/package-doctor-cli.d.ts +13 -0
  23. package/dist/commands/package-doctor-cli.d.ts.map +1 -0
  24. package/dist/commands/package-doctor-cli.js +80 -0
  25. package/dist/commands/package-doctor-cli.js.map +1 -0
  26. package/dist/commands/router-feedback-cli.d.ts +11 -0
  27. package/dist/commands/router-feedback-cli.d.ts.map +1 -0
  28. package/dist/commands/router-feedback-cli.js +99 -0
  29. package/dist/commands/router-feedback-cli.js.map +1 -0
  30. package/dist/commands/stats-cli.d.ts +20 -0
  31. package/dist/commands/stats-cli.d.ts.map +1 -0
  32. package/dist/commands/stats-cli.js +99 -0
  33. package/dist/commands/stats-cli.js.map +1 -0
  34. package/dist/core/agent-session-runtime.d.ts.map +1 -1
  35. package/dist/core/agent-session-runtime.js +4 -1
  36. package/dist/core/agent-session-runtime.js.map +1 -1
  37. package/dist/core/agent-session.d.ts +99 -31
  38. package/dist/core/agent-session.d.ts.map +1 -1
  39. package/dist/core/agent-session.js +624 -216
  40. package/dist/core/agent-session.js.map +1 -1
  41. package/dist/core/bash-executor.d.ts.map +1 -1
  42. package/dist/core/bash-executor.js +8 -2
  43. package/dist/core/bash-executor.js.map +1 -1
  44. package/dist/core/compaction/transaction.d.ts +9 -0
  45. package/dist/core/compaction/transaction.d.ts.map +1 -1
  46. package/dist/core/compaction/transaction.js +17 -2
  47. package/dist/core/compaction/transaction.js.map +1 -1
  48. package/dist/core/context-budget-governor-v2.d.ts +1 -1
  49. package/dist/core/context-budget-governor-v2.d.ts.map +1 -1
  50. package/dist/core/context-budget-governor-v2.js +1 -1
  51. package/dist/core/context-budget-governor-v2.js.map +1 -1
  52. package/dist/core/context-budget-system-prompt.d.ts.map +1 -1
  53. package/dist/core/context-budget-system-prompt.js +18 -2
  54. package/dist/core/context-budget-system-prompt.js.map +1 -1
  55. package/dist/core/context-budget-v2-cache-disk.d.ts +109 -0
  56. package/dist/core/context-budget-v2-cache-disk.d.ts.map +1 -0
  57. package/dist/core/context-budget-v2-cache-disk.js +343 -0
  58. package/dist/core/context-budget-v2-cache-disk.js.map +1 -0
  59. package/dist/core/context-budget-v2-cache-keys.d.ts +22 -0
  60. package/dist/core/context-budget-v2-cache-keys.d.ts.map +1 -1
  61. package/dist/core/context-budget-v2-cache-keys.js +4 -4
  62. package/dist/core/context-budget-v2-cache-keys.js.map +1 -1
  63. package/dist/core/context-budget-v2-cache.d.ts +1 -0
  64. package/dist/core/context-budget-v2-cache.d.ts.map +1 -1
  65. package/dist/core/context-budget-v2-cache.js +1 -0
  66. package/dist/core/context-budget-v2-cache.js.map +1 -1
  67. package/dist/core/context-budget-v2-planner.d.ts.map +1 -1
  68. package/dist/core/context-budget-v2-planner.js +17 -14
  69. package/dist/core/context-budget-v2-planner.js.map +1 -1
  70. package/dist/core/context-budget-v2-types.d.ts +1 -0
  71. package/dist/core/context-budget-v2-types.d.ts.map +1 -1
  72. package/dist/core/context-budget-v2-types.js.map +1 -1
  73. package/dist/core/extensions/loader.d.ts.map +1 -1
  74. package/dist/core/extensions/loader.js +45 -22
  75. package/dist/core/extensions/loader.js.map +1 -1
  76. package/dist/core/extensions/types.d.ts +4 -0
  77. package/dist/core/extensions/types.d.ts.map +1 -1
  78. package/dist/core/extensions/types.js.map +1 -1
  79. package/dist/core/mcp/client.d.ts +105 -0
  80. package/dist/core/mcp/client.d.ts.map +1 -0
  81. package/dist/core/mcp/client.js +198 -0
  82. package/dist/core/mcp/client.js.map +1 -0
  83. package/dist/core/mcp/config.d.ts +23 -0
  84. package/dist/core/mcp/config.d.ts.map +1 -0
  85. package/dist/core/mcp/config.js +89 -0
  86. package/dist/core/mcp/config.js.map +1 -0
  87. package/dist/core/mcp/index.d.ts +15 -0
  88. package/dist/core/mcp/index.d.ts.map +1 -0
  89. package/dist/core/mcp/index.js +15 -0
  90. package/dist/core/mcp/index.js.map +1 -0
  91. package/dist/core/mcp/manager.d.ts +84 -0
  92. package/dist/core/mcp/manager.d.ts.map +1 -0
  93. package/dist/core/mcp/manager.js +173 -0
  94. package/dist/core/mcp/manager.js.map +1 -0
  95. package/dist/core/mcp/protocol.d.ts +59 -0
  96. package/dist/core/mcp/protocol.d.ts.map +1 -0
  97. package/dist/core/mcp/protocol.js +103 -0
  98. package/dist/core/mcp/protocol.js.map +1 -0
  99. package/dist/core/mcp/stdio-transport.d.ts +63 -0
  100. package/dist/core/mcp/stdio-transport.d.ts.map +1 -0
  101. package/dist/core/mcp/stdio-transport.js +132 -0
  102. package/dist/core/mcp/stdio-transport.js.map +1 -0
  103. package/dist/core/mcp/tools.d.ts +47 -0
  104. package/dist/core/mcp/tools.d.ts.map +1 -0
  105. package/dist/core/mcp/tools.js +102 -0
  106. package/dist/core/mcp/tools.js.map +1 -0
  107. package/dist/core/package-archive.d.ts +3 -0
  108. package/dist/core/package-archive.d.ts.map +1 -0
  109. package/dist/core/package-archive.js +161 -0
  110. package/dist/core/package-archive.js.map +1 -0
  111. package/dist/core/package-doctor-source-scan.d.ts +13 -0
  112. package/dist/core/package-doctor-source-scan.d.ts.map +1 -0
  113. package/dist/core/package-doctor-source-scan.js +124 -0
  114. package/dist/core/package-doctor-source-scan.js.map +1 -0
  115. package/dist/core/package-doctor.d.ts +41 -0
  116. package/dist/core/package-doctor.d.ts.map +1 -0
  117. package/dist/core/package-doctor.js +145 -0
  118. package/dist/core/package-doctor.js.map +1 -0
  119. package/dist/core/package-manager.d.ts +10 -1
  120. package/dist/core/package-manager.d.ts.map +1 -1
  121. package/dist/core/package-manager.js +175 -42
  122. package/dist/core/package-manager.js.map +1 -1
  123. package/dist/core/package-manifest.d.ts +26 -0
  124. package/dist/core/package-manifest.d.ts.map +1 -0
  125. package/dist/core/package-manifest.js +85 -0
  126. package/dist/core/package-manifest.js.map +1 -0
  127. package/dist/core/pi-compat.d.ts +5 -0
  128. package/dist/core/pi-compat.d.ts.map +1 -0
  129. package/dist/core/pi-compat.js +18 -0
  130. package/dist/core/pi-compat.js.map +1 -0
  131. package/dist/core/provider-resilience.d.ts +7 -0
  132. package/dist/core/provider-resilience.d.ts.map +1 -1
  133. package/dist/core/provider-resilience.js +11 -0
  134. package/dist/core/provider-resilience.js.map +1 -1
  135. package/dist/core/reasoning-router-bias.d.ts +10 -14
  136. package/dist/core/reasoning-router-bias.d.ts.map +1 -1
  137. package/dist/core/reasoning-router-bias.js +22 -15
  138. package/dist/core/reasoning-router-bias.js.map +1 -1
  139. package/dist/core/reasoning-router-v4.d.ts +9 -1
  140. package/dist/core/reasoning-router-v4.d.ts.map +1 -1
  141. package/dist/core/reasoning-router-v4.js +20 -5
  142. package/dist/core/reasoning-router-v4.js.map +1 -1
  143. package/dist/core/redaction.d.ts +2 -5
  144. package/dist/core/redaction.d.ts.map +1 -1
  145. package/dist/core/redaction.js +14 -1
  146. package/dist/core/redaction.js.map +1 -1
  147. package/dist/core/repository-learning-scope.d.ts +10 -0
  148. package/dist/core/repository-learning-scope.d.ts.map +1 -0
  149. package/dist/core/repository-learning-scope.js +42 -0
  150. package/dist/core/repository-learning-scope.js.map +1 -0
  151. package/dist/core/resource-loader.d.ts.map +1 -1
  152. package/dist/core/resource-loader.js +16 -1
  153. package/dist/core/resource-loader.js.map +1 -1
  154. package/dist/core/router-feedback-collector.d.ts +2 -1
  155. package/dist/core/router-feedback-collector.d.ts.map +1 -1
  156. package/dist/core/router-feedback-collector.js +2 -2
  157. package/dist/core/router-feedback-collector.js.map +1 -1
  158. package/dist/core/runtime-provenance.d.ts +54 -0
  159. package/dist/core/runtime-provenance.d.ts.map +1 -0
  160. package/dist/core/runtime-provenance.js +115 -0
  161. package/dist/core/runtime-provenance.js.map +1 -0
  162. package/dist/core/sandbox/backend.d.ts.map +1 -1
  163. package/dist/core/sandbox/backend.js +39 -21
  164. package/dist/core/sandbox/backend.js.map +1 -1
  165. package/dist/core/sandbox/default-policy.d.ts +4 -5
  166. package/dist/core/sandbox/default-policy.d.ts.map +1 -1
  167. package/dist/core/sandbox/default-policy.js +9 -11
  168. package/dist/core/sandbox/default-policy.js.map +1 -1
  169. package/dist/core/session-bash-runtime.d.ts +4 -4
  170. package/dist/core/session-bash-runtime.d.ts.map +1 -1
  171. package/dist/core/session-bash-runtime.js +6 -5
  172. package/dist/core/session-bash-runtime.js.map +1 -1
  173. package/dist/core/session-compaction-service.d.ts.map +1 -1
  174. package/dist/core/session-compaction-service.js +7 -7
  175. package/dist/core/session-compaction-service.js.map +1 -1
  176. package/dist/core/session-manager.d.ts +4 -1
  177. package/dist/core/session-manager.d.ts.map +1 -1
  178. package/dist/core/session-manager.js +47 -7
  179. package/dist/core/session-manager.js.map +1 -1
  180. package/dist/core/settings-manager.d.ts +7 -4
  181. package/dist/core/settings-manager.d.ts.map +1 -1
  182. package/dist/core/settings-manager.js +11 -3
  183. package/dist/core/settings-manager.js.map +1 -1
  184. package/dist/core/subagent-orchestration.d.ts.map +1 -1
  185. package/dist/core/subagent-orchestration.js +4 -2
  186. package/dist/core/subagent-orchestration.js.map +1 -1
  187. package/dist/core/system-prompt.d.ts +9 -1
  188. package/dist/core/system-prompt.d.ts.map +1 -1
  189. package/dist/core/system-prompt.js +10 -4
  190. package/dist/core/system-prompt.js.map +1 -1
  191. package/dist/core/tools/bash.d.ts +2 -2
  192. package/dist/core/tools/bash.d.ts.map +1 -1
  193. package/dist/core/tools/bash.js +2 -1
  194. package/dist/core/tools/bash.js.map +1 -1
  195. package/dist/core/tools/diagnostics.d.ts.map +1 -1
  196. package/dist/core/tools/diagnostics.js +8 -2
  197. package/dist/core/tools/diagnostics.js.map +1 -1
  198. package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
  199. package/dist/core/tools/tool-definition-wrapper.js +15 -0
  200. package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
  201. package/dist/core/turn-metrics.d.ts +135 -0
  202. package/dist/core/turn-metrics.d.ts.map +1 -0
  203. package/dist/core/turn-metrics.js +261 -0
  204. package/dist/core/turn-metrics.js.map +1 -0
  205. package/dist/core/verified-bash-runtime.d.ts.map +1 -1
  206. package/dist/core/verified-bash-runtime.js +10 -4
  207. package/dist/core/verified-bash-runtime.js.map +1 -1
  208. package/dist/guardrails/evidence-protocol.d.ts +10 -0
  209. package/dist/guardrails/evidence-protocol.d.ts.map +1 -0
  210. package/dist/guardrails/evidence-protocol.js +34 -0
  211. package/dist/guardrails/evidence-protocol.js.map +1 -0
  212. package/dist/guardrails/evidence-system.d.ts +3 -1
  213. package/dist/guardrails/evidence-system.d.ts.map +1 -1
  214. package/dist/guardrails/evidence-system.js +22 -14
  215. package/dist/guardrails/evidence-system.js.map +1 -1
  216. package/dist/guardrails/replay-ledger-lock-owner.d.ts +7 -0
  217. package/dist/guardrails/replay-ledger-lock-owner.d.ts.map +1 -1
  218. package/dist/guardrails/replay-ledger-lock-owner.js +42 -1
  219. package/dist/guardrails/replay-ledger-lock-owner.js.map +1 -1
  220. package/dist/guardrails/replay-payload-hash.d.ts +6 -0
  221. package/dist/guardrails/replay-payload-hash.d.ts.map +1 -0
  222. package/dist/guardrails/replay-payload-hash.js +24 -0
  223. package/dist/guardrails/replay-payload-hash.js.map +1 -0
  224. package/dist/index.d.ts +8 -2
  225. package/dist/index.d.ts.map +1 -1
  226. package/dist/index.js +7 -1
  227. package/dist/index.js.map +1 -1
  228. package/dist/main.d.ts.map +1 -1
  229. package/dist/main.js +18 -0
  230. package/dist/main.js.map +1 -1
  231. package/dist/modes/interactive/components/control-panel-layout.d.ts.map +1 -1
  232. package/dist/modes/interactive/components/control-panel-layout.js +2 -2
  233. package/dist/modes/interactive/components/control-panel-layout.js.map +1 -1
  234. package/dist/modes/interactive/components/footer.d.ts +3 -0
  235. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  236. package/dist/modes/interactive/components/footer.js +40 -12
  237. package/dist/modes/interactive/components/footer.js.map +1 -1
  238. package/dist/modes/interactive/components/settings-selector.d.ts +2 -0
  239. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  240. package/dist/modes/interactive/components/settings-selector.js +10 -0
  241. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  242. package/dist/modes/interactive/components/status-sidebar.d.ts +14 -2
  243. package/dist/modes/interactive/components/status-sidebar.d.ts.map +1 -1
  244. package/dist/modes/interactive/components/status-sidebar.js +106 -19
  245. package/dist/modes/interactive/components/status-sidebar.js.map +1 -1
  246. package/dist/modes/interactive/interactive-mode.d.ts +10 -1
  247. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  248. package/dist/modes/interactive/interactive-mode.js +104 -17
  249. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  250. package/dist/modes/interactive/theme/dark.json +1 -1
  251. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  252. package/dist/modes/interactive/theme/theme.js +1 -0
  253. package/dist/modes/interactive/theme/theme.js.map +1 -1
  254. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  255. package/dist/modes/rpc/rpc-mode.js +1 -1
  256. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  257. package/dist/types/evidence.d.ts +11 -3
  258. package/dist/types/evidence.d.ts.map +1 -1
  259. package/dist/types/evidence.js.map +1 -1
  260. package/docs/book-to-skill.md +79 -0
  261. package/docs/compaction.md +35 -3
  262. package/docs/containerization.md +14 -2
  263. package/docs/docs.json +16 -0
  264. package/docs/environment-variables.md +1 -1
  265. package/docs/extensions.md +75 -16
  266. package/docs/index.md +3 -0
  267. package/docs/mcp.md +97 -0
  268. package/docs/metrics.md +87 -0
  269. package/docs/models.md +12 -0
  270. package/docs/packages.md +25 -4
  271. package/docs/provider-resilience.md +37 -56
  272. package/docs/providers.md +5 -1
  273. package/docs/run-protocol.md +72 -0
  274. package/docs/sdk.md +40 -6
  275. package/docs/sessions.md +6 -0
  276. package/docs/settings.md +10 -7
  277. package/docs/skills.md +9 -1
  278. package/docs/usage.md +14 -4
  279. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  280. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  281. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  282. package/examples/extensions/gondolin/package-lock.json +2 -2
  283. package/examples/extensions/gondolin/package.json +1 -1
  284. package/examples/extensions/plannotator-approval-bridge.ts +83 -0
  285. package/examples/extensions/sandbox/package-lock.json +2 -2
  286. package/examples/extensions/sandbox/package.json +1 -1
  287. package/examples/extensions/subagent/README.md +21 -11
  288. package/examples/extensions/subagent/adaptive-agent-runtime.ts +9 -3
  289. package/examples/extensions/subagent/agents/planner.md +0 -2
  290. package/examples/extensions/subagent/agents/reviewer.md +0 -2
  291. package/examples/extensions/subagent/agents/scout.md +0 -2
  292. package/examples/extensions/subagent/agents/worker.md +0 -1
  293. package/examples/extensions/subagent/agents.test.ts +9 -0
  294. package/examples/extensions/subagent/agents.ts +11 -4
  295. package/examples/extensions/subagent/deadline-budget.test.ts +32 -0
  296. package/examples/extensions/subagent/deadline-budget.ts +25 -1
  297. package/examples/extensions/subagent/fixtures/fake-omk-json.mjs +10 -0
  298. package/examples/extensions/subagent/index.ts +234 -55
  299. package/examples/extensions/subagent/subagent-execution-policy.test.ts +27 -0
  300. package/examples/extensions/subagent/subagent-extension-smoke.test.ts +167 -2
  301. package/examples/extensions/subagent/workflow-graph.test.ts +80 -0
  302. package/examples/extensions/subagent/workflow-graph.ts +89 -0
  303. package/examples/extensions/with-deps/package-lock.json +2 -2
  304. package/examples/extensions/with-deps/package.json +1 -1
  305. package/npm-shrinkwrap.json +275 -439
  306. package/package.json +11 -8
@@ -4,10 +4,11 @@
4
4
  * Spawns a separate `omk` process for each subagent invocation,
5
5
  * giving it an isolated context window.
6
6
  *
7
- * Supports three modes:
7
+ * Supports four modes:
8
8
  * - Single: { agent: "name", task: "..." }
9
9
  * - Parallel: { tasks: [{ agent: "name", task: "..." }, ...] }
10
10
  * - Chain: { chain: [{ agent: "name", task: "... {previous} ..." }, ...] }
11
+ * - Graph: { graph: [{ id: "research", agent: "scout", task: "...", dependsOn: [] }, ...] }
11
12
  *
12
13
  * Uses JSON mode to capture structured output from subagents.
13
14
  */
@@ -33,13 +34,24 @@ import {
33
34
  parseEmbeddedCapabilities,
34
35
  validateCapabilities,
35
36
  } from "./capabilities.ts";
36
- import { createExecutionBudget, type ExecutionBudget, remainingExecutionMs } from "./deadline-budget.ts";
37
+ import {
38
+ createExecutionBudget,
39
+ type ExecutionBudget,
40
+ exceedsParallelTaskLimit,
41
+ remainingExecutionMs,
42
+ resolveSubagentExecutionPolicy,
43
+ } from "./deadline-budget.ts";
37
44
  import { DeadlineProfileStore } from "./deadline-profile-store.ts";
38
45
  import { runManagedProcess } from "./managed-process.ts";
39
46
  import { emptyUsage, type SingleResult, type SubagentAttemptResult } from "./subagent-runtime-types.ts";
47
+ import {
48
+ type GraphTask,
49
+ GraphValidationError,
50
+ planTaskGraph,
51
+ renderDependencyContext,
52
+ type TaskGraphPlan,
53
+ } from "./workflow-graph.ts";
40
54
 
41
- const MAX_PARALLEL_TASKS = 8;
42
- const MAX_CONCURRENCY = 4;
43
55
  const COLLAPSED_ITEM_COUNT = 10;
44
56
  const PER_TASK_OUTPUT_CAP = 50 * 1024;
45
57
  const DEFAULT_EXECUTION_BUDGET_MS = 840_000;
@@ -173,6 +185,7 @@ function formatToolCall(
173
185
  }
174
186
 
175
187
  interface ExecutionBudgetDetails {
188
+ unbounded: boolean;
176
189
  hardDeadlineMs: number;
177
190
  elapsedMs: number;
178
191
  remainingMs: number;
@@ -183,18 +196,24 @@ interface ExecutionBudgetDetails {
183
196
  }
184
197
 
185
198
  interface SubagentDetails {
186
- mode: "single" | "parallel" | "chain";
199
+ mode: "single" | "parallel" | "chain" | "graph";
187
200
  agentScope: AgentScope;
188
201
  projectAgentsDir: string | null;
189
202
  results: SingleResult[];
190
203
  executionBudget: ExecutionBudgetDetails;
204
+ graph?: { waves: readonly (readonly string[])[]; completedNodeIds: readonly string[] };
191
205
  }
192
206
 
193
- function summarizeExecutionBudget(budget: ExecutionBudget, results: readonly SingleResult[]): ExecutionBudgetDetails {
207
+ function summarizeExecutionBudget(
208
+ budget: ExecutionBudget,
209
+ results: readonly SingleResult[],
210
+ unbounded: boolean,
211
+ ): ExecutionBudgetDetails {
194
212
  return {
195
- hardDeadlineMs: budget.hardDeadlineMs,
213
+ unbounded,
214
+ hardDeadlineMs: unbounded ? 0 : budget.hardDeadlineMs,
196
215
  elapsedMs: Date.now() - budget.startedAtMs,
197
- remainingMs: remainingExecutionMs(budget),
216
+ remainingMs: unbounded ? 0 : remainingExecutionMs(budget),
198
217
  estimatedTokens: results.reduce((sum, result) => sum + (result.deadline?.estimatedTokens ?? 0), 0),
199
218
  plannedShards: results.reduce((sum, result) => sum + (result.deadline?.plannedShardIds.length ?? 0), 0),
200
219
  completedShards: results.reduce((sum, result) => sum + (result.deadline?.completedShardIds.length ?? 0), 0),
@@ -263,15 +282,16 @@ async function mapWithConcurrencyLimit<TIn, TOut>(
263
282
  ): Promise<TOut[]> {
264
283
  if (items.length === 0) return [];
265
284
  const limit = Math.max(1, Math.min(concurrency, items.length));
266
- const results: TOut[] = new Array(items.length);
285
+ const results: TOut[] = [];
267
286
  let nextIndex = 0;
268
- const workers = new Array(limit).fill(null).map(async () => {
269
- while (true) {
287
+ const runWorker = async (): Promise<void> => {
288
+ while (nextIndex < items.length) {
270
289
  const current = nextIndex++;
271
- if (current >= items.length) return;
272
290
  results[current] = await fn(items[current], current);
273
291
  }
274
- });
292
+ };
293
+ const workers: Promise<void>[] = [];
294
+ for (let worker = 0; worker < limit; worker++) workers.push(runWorker());
275
295
  await Promise.all(workers);
276
296
  return results;
277
297
  }
@@ -515,6 +535,7 @@ async function runSingleAgent(
515
535
  signal: AbortSignal | undefined,
516
536
  onUpdate: OnUpdateCallback | undefined,
517
537
  makeDetails: (results: SingleResult[]) => SubagentDetails,
538
+ unbounded: boolean,
518
539
  budget: ExecutionBudget,
519
540
  profileStore: DeadlineProfileStore,
520
541
  pendingSequentialTasks: number,
@@ -533,6 +554,18 @@ async function runSingleAgent(
533
554
  step,
534
555
  };
535
556
  }
557
+ const reportUpdate: ResultUpdateCallback | undefined = onUpdate
558
+ ? (partial) =>
559
+ onUpdate({
560
+ content: [{ type: "text", text: getResultOutput(partial) || "(running...)" }],
561
+ details: makeDetails([partial]),
562
+ })
563
+ : undefined;
564
+ if (unbounded) {
565
+ return (
566
+ await runSingleAgentAttempt(defaultCwd, agents, agentName, task, task, cwd, step, 0, signal, reportUpdate)
567
+ ).result;
568
+ }
536
569
  return await runAdaptiveAgent({
537
570
  agentName,
538
571
  agentSource: agent.source,
@@ -548,13 +581,7 @@ async function runSingleAgent(
548
581
  minimumAttemptMs: MINIMUM_ATTEMPT_MS,
549
582
  maxTokensPerShard: MAX_TOKENS_PER_TASK_SHARD,
550
583
  },
551
- onUpdate: onUpdate
552
- ? (partial) =>
553
- onUpdate({
554
- content: [{ type: "text", text: getResultOutput(partial) || "(running...)" }],
555
- details: makeDetails([partial]),
556
- })
557
- : undefined,
584
+ onUpdate: reportUpdate,
558
585
  runAttempt: async (input: RunAttemptInput) =>
559
586
  await runSingleAgentAttempt(
560
587
  defaultCwd,
@@ -583,6 +610,14 @@ const ChainItem = Type.Object({
583
610
  cwd: Type.Optional(Type.String({ description: "Working directory for the agent process" })),
584
611
  });
585
612
 
613
+ const GraphItem = Type.Object({
614
+ id: Type.String({ description: "Unique task node id" }),
615
+ agent: Type.String({ description: "Name of the agent to invoke" }),
616
+ task: Type.String({ description: "Task; use {dependencies} to inject declared dependency outputs" }),
617
+ cwd: Type.Optional(Type.String({ description: "Working directory for the agent process" })),
618
+ dependsOn: Type.Optional(Type.Array(Type.String(), { description: "Task node ids that must complete first" })),
619
+ });
620
+
586
621
  const AgentScopeSchema = StringEnum(["user", "project", "both"] as const, {
587
622
  description: 'Which agent directories to use. Default: "user". Use "both" to include project-local agents.',
588
623
  default: "user",
@@ -593,6 +628,7 @@ const SubagentParams = Type.Object({
593
628
  task: Type.Optional(Type.String({ description: "Task to delegate (for single mode)" })),
594
629
  tasks: Type.Optional(Type.Array(TaskItem, { description: "Array of {agent, task} for parallel execution" })),
595
630
  chain: Type.Optional(Type.Array(ChainItem, { description: "Array of {agent, task} for sequential execution" })),
631
+ graph: Type.Optional(Type.Array(GraphItem, { description: "Validated dependency DAG executed in parallel waves" })),
596
632
  agentScope: Type.Optional(AgentScopeSchema),
597
633
  confirmProjectAgents: Type.Optional(
598
634
  Type.Boolean({ description: "Prompt before running project-local agents. Default: true.", default: true }),
@@ -600,7 +636,7 @@ const SubagentParams = Type.Object({
600
636
  executionBudgetMs: Type.Optional(
601
637
  Type.Integer({
602
638
  description:
603
- "Internal wall-clock budget. Defaults to 840000ms, leaving cleanup time before the 900000ms tool timeout.",
639
+ "Internal wall-clock budget. Defaults to 840000ms outside Ultra; supplying it also bounds Ultra execution.",
604
640
  minimum: 60_000,
605
641
  maximum: MAX_EXECUTION_BUDGET_MS,
606
642
  default: DEFAULT_EXECUTION_BUDGET_MS,
@@ -623,18 +659,27 @@ export default function (omk: ExtensionAPI) {
623
659
  label: "Subagent",
624
660
  description: [
625
661
  "Delegate tasks to specialized subagents with isolated context.",
626
- "Modes: single (agent + task), parallel (tasks array), chain (sequential with {previous} placeholder).",
627
- "Long work is predictively sharded by token and wall-clock demand, checkpointed, and resumed only from unfinished work.",
662
+ "Modes: single, parallel, chain, or graph (validated dependency DAG with parallel waves and {dependencies} handoff).",
663
+ "Long work is predictively sharded by token and wall-clock demand, checkpointed, and resumed only from unfinished work; Ultra removes task-count and concurrency caps, plus the execution deadline unless executionBudgetMs is supplied.",
628
664
  'Default agent scope is "user" (from ~/.omk/agent/agents).',
629
665
  'To enable project-local agents in .omk/agents, set agentScope: "both" (or "project").',
630
666
  ].join(" "),
631
667
  parameters: SubagentParams,
668
+ resolveTimeoutMs: (ctx) => (ctx.thinkingLevel === "ultra" ? 0 : undefined),
632
669
 
633
670
  async execute(_toolCallId, params, signal, onUpdate, ctx) {
634
671
  const agentScope: AgentScope = params.agentScope ?? "user";
635
672
  const discovery = discoverAgents(ctx.cwd, agentScope);
636
- const agents = discovery.agents;
673
+ const selectedModel = ctx.model ? `${ctx.model.provider}/${ctx.model.id}` : undefined;
674
+ const agents = discovery.agents.map((agent) =>
675
+ agent.model || !selectedModel ? agent : { ...agent, model: selectedModel },
676
+ );
637
677
  const confirmProjectAgents = params.confirmProjectAgents ?? true;
678
+ const executionPolicy = resolveSubagentExecutionPolicy(
679
+ ctx.thinkingLevel,
680
+ params.tasks?.length ?? params.graph?.length ?? 1,
681
+ params.executionBudgetMs !== undefined,
682
+ );
638
683
  const executionBudget = createExecutionBudget({
639
684
  hardDeadlineMs: params.executionBudgetMs ?? DEFAULT_EXECUTION_BUDGET_MS,
640
685
  cleanupReserveMs: EXECUTION_CLEANUP_RESERVE_MS,
@@ -645,17 +690,20 @@ export default function (omk: ExtensionAPI) {
645
690
 
646
691
  const hasChain = (params.chain?.length ?? 0) > 0;
647
692
  const hasTasks = (params.tasks?.length ?? 0) > 0;
693
+ const hasGraph = (params.graph?.length ?? 0) > 0;
648
694
  const hasSingle = Boolean(params.agent && params.task);
649
- const modeCount = Number(hasChain) + Number(hasTasks) + Number(hasSingle);
695
+ const modeCount = Number(hasChain) + Number(hasTasks) + Number(hasGraph) + Number(hasSingle);
650
696
 
697
+ let graphDetails: SubagentDetails["graph"];
651
698
  const makeDetails =
652
- (mode: "single" | "parallel" | "chain") =>
699
+ (mode: SubagentDetails["mode"]) =>
653
700
  (results: SingleResult[]): SubagentDetails => ({
654
701
  mode,
655
702
  agentScope,
656
703
  projectAgentsDir: discovery.projectAgentsDir,
657
704
  results,
658
- executionBudget: summarizeExecutionBudget(executionBudget, results),
705
+ executionBudget: summarizeExecutionBudget(executionBudget, results, executionPolicy.unbounded),
706
+ ...(mode === "graph" && graphDetails ? { graph: graphDetails } : {}),
659
707
  });
660
708
 
661
709
  if (modeCount !== 1) {
@@ -675,6 +723,7 @@ export default function (omk: ExtensionAPI) {
675
723
  const requestedAgentNames = new Set<string>();
676
724
  if (params.chain) for (const step of params.chain) requestedAgentNames.add(step.agent);
677
725
  if (params.tasks) for (const t of params.tasks) requestedAgentNames.add(t.agent);
726
+ if (params.graph) for (const node of params.graph) requestedAgentNames.add(node.agent);
678
727
  if (params.agent) requestedAgentNames.add(params.agent);
679
728
 
680
729
  const projectAgentsRequested = Array.from(requestedAgentNames)
@@ -691,7 +740,9 @@ export default function (omk: ExtensionAPI) {
691
740
  if (!ok)
692
741
  return {
693
742
  content: [{ type: "text", text: "Canceled: project-local agents not approved." }],
694
- details: makeDetails(hasChain ? "chain" : hasTasks ? "parallel" : "single")([]),
743
+ details: makeDetails(hasChain ? "chain" : hasTasks ? "parallel" : hasGraph ? "graph" : "single")(
744
+ [],
745
+ ),
695
746
  };
696
747
  }
697
748
  }
@@ -729,6 +780,7 @@ export default function (omk: ExtensionAPI) {
729
780
  signal,
730
781
  chainUpdate,
731
782
  makeDetails("chain"),
783
+ executionPolicy.unbounded,
732
784
  executionBudget,
733
785
  profileStore,
734
786
  params.chain.length - i,
@@ -752,13 +804,104 @@ export default function (omk: ExtensionAPI) {
752
804
  };
753
805
  }
754
806
 
807
+ if (params.graph && params.graph.length > 0) {
808
+ let plan: TaskGraphPlan;
809
+ try {
810
+ plan = planTaskGraph(params.graph as GraphTask[]);
811
+ } catch (error) {
812
+ const message = error instanceof GraphValidationError ? error.message : "Invalid task graph";
813
+ return {
814
+ content: [{ type: "text", text: message }],
815
+ details: makeDetails("graph")([]),
816
+ isError: true,
817
+ };
818
+ }
819
+ if (exceedsParallelTaskLimit(executionPolicy, params.graph.length)) {
820
+ return {
821
+ content: [
822
+ {
823
+ type: "text",
824
+ text: `Too many graph tasks (${params.graph.length}). Max is ${executionPolicy.maxParallelTasks}.`,
825
+ },
826
+ ],
827
+ details: makeDetails("graph")([]),
828
+ };
829
+ }
830
+
831
+ const results: SingleResult[] = [];
832
+ const outputs = new Map<string, string>();
833
+ const completedNodeIds: string[] = [];
834
+ graphDetails = { waves: plan.waves, completedNodeIds };
835
+ for (const [waveIndex, wave] of plan.waves.entries()) {
836
+ const waveResults = await mapWithConcurrencyLimit<string, { id: string; result: SingleResult }>(
837
+ [...wave],
838
+ executionPolicy.concurrency,
839
+ async (id) => {
840
+ const task = plan.tasks.get(id);
841
+ if (!task) throw new GraphValidationError(`node '${id}' disappeared after validation`);
842
+ const result = await runSingleAgent(
843
+ ctx.cwd,
844
+ agents,
845
+ task.agent,
846
+ renderDependencyContext(task, outputs),
847
+ task.cwd,
848
+ undefined,
849
+ signal,
850
+ undefined,
851
+ makeDetails("graph"),
852
+ executionPolicy.unbounded,
853
+ executionBudget,
854
+ profileStore,
855
+ plan.waves.length - waveIndex,
856
+ );
857
+ return { id, result };
858
+ },
859
+ );
860
+ for (const { id, result } of waveResults) {
861
+ results.push(result);
862
+ if (!isFailedResult(result)) {
863
+ outputs.set(id, getResultOutput(result));
864
+ completedNodeIds.push(id);
865
+ }
866
+ }
867
+ graphDetails = { waves: plan.waves, completedNodeIds: [...completedNodeIds] };
868
+ const failed = waveResults.filter(({ result }) => isFailedResult(result));
869
+ if (failed.length > 0) {
870
+ const failedIds = failed.map(({ id }) => id).join(", ");
871
+ return {
872
+ content: [
873
+ {
874
+ type: "text",
875
+ text: `Graph stopped after wave ${waveIndex + 1}; failed nodes: ${failedIds}. Downstream nodes were not started.`,
876
+ },
877
+ ],
878
+ details: makeDetails("graph")(results),
879
+ isError: true,
880
+ };
881
+ }
882
+ }
883
+ const summaries = results.map((result, index) => {
884
+ const id = completedNodeIds[index] ?? `node-${index + 1}`;
885
+ return `### [${id}] completed\n\n${truncateParallelOutput(getResultOutput(result))}`;
886
+ });
887
+ return {
888
+ content: [
889
+ {
890
+ type: "text",
891
+ text: `Graph: ${results.length}/${params.graph.length} completed in ${plan.waves.length} waves\n\n${summaries.join("\n\n---\n\n")}`,
892
+ },
893
+ ],
894
+ details: makeDetails("graph")(results),
895
+ };
896
+ }
897
+
755
898
  if (params.tasks && params.tasks.length > 0) {
756
- if (params.tasks.length > MAX_PARALLEL_TASKS)
899
+ if (exceedsParallelTaskLimit(executionPolicy, params.tasks.length))
757
900
  return {
758
901
  content: [
759
902
  {
760
903
  type: "text",
761
- text: `Too many parallel tasks (${params.tasks.length}). Max is ${MAX_PARALLEL_TASKS}.`,
904
+ text: `Too many parallel tasks (${params.tasks.length}). Max is ${executionPolicy.maxParallelTasks}.`,
762
905
  },
763
906
  ],
764
907
  details: makeDetails("parallel")([]),
@@ -793,31 +936,36 @@ export default function (omk: ExtensionAPI) {
793
936
  }
794
937
  };
795
938
 
796
- const results = await mapWithConcurrencyLimit(params.tasks, MAX_CONCURRENCY, async (t, index) => {
797
- const result = await runSingleAgent(
798
- ctx.cwd,
799
- agents,
800
- t.agent,
801
- t.task,
802
- t.cwd,
803
- undefined,
804
- signal,
805
- // Per-task update callback
806
- (partial) => {
807
- if (partial.details?.results[0]) {
808
- allResults[index] = partial.details.results[0];
809
- emitParallelUpdate();
810
- }
811
- },
812
- makeDetails("parallel"),
813
- executionBudget,
814
- profileStore,
815
- 1,
816
- );
817
- allResults[index] = result;
818
- emitParallelUpdate();
819
- return result;
820
- });
939
+ const results = await mapWithConcurrencyLimit(
940
+ params.tasks,
941
+ executionPolicy.concurrency,
942
+ async (t, index) => {
943
+ const result = await runSingleAgent(
944
+ ctx.cwd,
945
+ agents,
946
+ t.agent,
947
+ t.task,
948
+ t.cwd,
949
+ undefined,
950
+ signal,
951
+ // Per-task update callback
952
+ (partial) => {
953
+ if (partial.details?.results[0]) {
954
+ allResults[index] = partial.details.results[0];
955
+ emitParallelUpdate();
956
+ }
957
+ },
958
+ makeDetails("parallel"),
959
+ executionPolicy.unbounded,
960
+ executionBudget,
961
+ profileStore,
962
+ 1,
963
+ );
964
+ allResults[index] = result;
965
+ emitParallelUpdate();
966
+ return result;
967
+ },
968
+ );
821
969
 
822
970
  const successCount = results.filter((r) => !isFailedResult(r)).length;
823
971
  const summaries = results.map((r) => {
@@ -849,6 +997,7 @@ export default function (omk: ExtensionAPI) {
849
997
  signal,
850
998
  onUpdate,
851
999
  makeDetails("single"),
1000
+ executionPolicy.unbounded,
852
1001
  executionBudget,
853
1002
  profileStore,
854
1003
  1,
@@ -897,6 +1046,18 @@ export default function (omk: ExtensionAPI) {
897
1046
  if (args.chain.length > 3) text += `\n ${theme.fg("muted", `... +${args.chain.length - 3} more`)}`;
898
1047
  return new Text(text, 0, 0);
899
1048
  }
1049
+ if (args.graph && args.graph.length > 0) {
1050
+ let text =
1051
+ theme.fg("toolTitle", theme.bold("subagent ")) +
1052
+ theme.fg("accent", `graph (${args.graph.length} nodes)`) +
1053
+ theme.fg("muted", ` [${scope}]`);
1054
+ for (const node of args.graph.slice(0, 3)) {
1055
+ const dependencies = node.dependsOn?.length ? ` ← ${node.dependsOn.join(",")}` : "";
1056
+ text += `\n ${theme.fg("accent", node.id)}${theme.fg("dim", ` @${node.agent}${dependencies}`)}`;
1057
+ }
1058
+ if (args.graph.length > 3) text += `\n ${theme.fg("muted", `... +${args.graph.length - 3} more`)}`;
1059
+ return new Text(text, 0, 0);
1060
+ }
900
1061
  if (args.tasks && args.tasks.length > 0) {
901
1062
  let text =
902
1063
  theme.fg("toolTitle", theme.bold("subagent ")) +
@@ -1015,6 +1176,24 @@ export default function (omk: ExtensionAPI) {
1015
1176
  return total;
1016
1177
  };
1017
1178
 
1179
+ if (details.mode === "graph") {
1180
+ const completed = details.graph?.completedNodeIds.length ?? 0;
1181
+ const total =
1182
+ details.graph?.waves.reduce((count, wave) => count + wave.length, 0) ?? details.results.length;
1183
+ const waves = details.graph?.waves.length ?? 0;
1184
+ const failed = details.results.some(isFailedResult);
1185
+ const icon = failed ? theme.fg("error", "✗") : theme.fg("success", "✓");
1186
+ let text = `${icon} ${theme.fg("toolTitle", theme.bold("graph "))}${theme.fg("accent", `${completed}/${total} nodes · ${waves} waves`)}`;
1187
+ for (const [index, result] of details.results.entries()) {
1188
+ const id = details.graph?.completedNodeIds[index] ?? result.agent;
1189
+ const resultIcon = isFailedResult(result) ? theme.fg("error", "✗") : theme.fg("success", "✓");
1190
+ text += `\n${theme.fg("muted", "─── ")}${theme.fg("accent", id)} ${resultIcon}`;
1191
+ if (expanded) text += `\n${theme.fg("toolOutput", getResultOutput(result))}`;
1192
+ }
1193
+ if (!expanded) text += `\n${theme.fg("muted", "(Ctrl+O to expand)")}`;
1194
+ return new Text(text, 0, 0);
1195
+ }
1196
+
1018
1197
  if (details.mode === "chain") {
1019
1198
  const successCount = details.results.filter((r) => r.exitCode === 0).length;
1020
1199
  const icon = successCount === details.results.length ? theme.fg("success", "✓") : theme.fg("error", "✗");
@@ -0,0 +1,27 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { exceedsParallelTaskLimit, resolveSubagentExecutionPolicy } from "./deadline-budget.ts";
3
+
4
+ describe("subagent execution policy", () => {
5
+ it("keeps the bounded defaults outside Ultra", () => {
6
+ const policy = resolveSubagentExecutionPolicy("high", 12);
7
+
8
+ expect(policy).toEqual({ unbounded: false, maxParallelTasks: 8, concurrency: 4 });
9
+ expect(exceedsParallelTaskLimit(policy, 9)).toBe(true);
10
+ });
11
+
12
+ it("removes task, concurrency, and deadline limits in Ultra", () => {
13
+ const policy = resolveSubagentExecutionPolicy("ultra", 12);
14
+
15
+ expect(policy).toEqual({ unbounded: true, maxParallelTasks: undefined, concurrency: 12 });
16
+ expect(exceedsParallelTaskLimit(policy, 12)).toBe(false);
17
+ });
18
+
19
+ it("keeps the Ultra deadline bounded when an execution budget is explicit", () => {
20
+ // Given an Ultra subagent call with an explicit wall-clock budget
21
+ // When its execution policy is resolved
22
+ const policy = resolveSubagentExecutionPolicy("ultra", 12, true);
23
+
24
+ // Then only the deadline remains bounded; Ultra still removes task and concurrency limits
25
+ expect(policy).toEqual({ unbounded: false, maxParallelTasks: undefined, concurrency: 12 });
26
+ });
27
+ });