open-multi-agent-kit 0.98.5 → 1.0.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 (385) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/README.md +4 -3
  3. package/dist/bun/cli.d.ts.map +1 -1
  4. package/dist/bun/cli.js +1 -0
  5. package/dist/bun/cli.js.map +1 -1
  6. package/dist/bun/register-bundled-coding-agent.d.ts +10 -0
  7. package/dist/bun/register-bundled-coding-agent.d.ts.map +1 -0
  8. package/dist/bun/register-bundled-coding-agent.js +12 -0
  9. package/dist/bun/register-bundled-coding-agent.js.map +1 -0
  10. package/dist/cli/args.d.ts +1 -1
  11. package/dist/cli/args.d.ts.map +1 -1
  12. package/dist/cli/args.js +1 -1
  13. package/dist/cli/args.js.map +1 -1
  14. package/dist/cli/help.d.ts.map +1 -1
  15. package/dist/cli/help.js +2 -1
  16. package/dist/cli/help.js.map +1 -1
  17. package/dist/cli.d.ts.map +1 -1
  18. package/dist/cli.js +14 -2
  19. package/dist/cli.js.map +1 -1
  20. package/dist/commands/neo-cli.d.ts +9 -0
  21. package/dist/commands/neo-cli.d.ts.map +1 -0
  22. package/dist/commands/neo-cli.js +61 -0
  23. package/dist/commands/neo-cli.js.map +1 -0
  24. package/dist/commands/verified-run-cli.d.ts.map +1 -1
  25. package/dist/commands/verified-run-cli.js +2 -2
  26. package/dist/commands/verified-run-cli.js.map +1 -1
  27. package/dist/core/agent-session.d.ts +29 -2
  28. package/dist/core/agent-session.d.ts.map +1 -1
  29. package/dist/core/agent-session.js +126 -32
  30. package/dist/core/agent-session.js.map +1 -1
  31. package/dist/core/bundled-skills.d.ts +4 -0
  32. package/dist/core/bundled-skills.d.ts.map +1 -0
  33. package/dist/core/bundled-skills.js +32 -0
  34. package/dist/core/bundled-skills.js.map +1 -0
  35. package/dist/core/cli-diagnostics.d.ts +6 -0
  36. package/dist/core/cli-diagnostics.d.ts.map +1 -0
  37. package/dist/core/cli-diagnostics.js +20 -0
  38. package/dist/core/cli-diagnostics.js.map +1 -0
  39. package/dist/core/context-budget-headroom.d.ts +12 -0
  40. package/dist/core/context-budget-headroom.d.ts.map +1 -1
  41. package/dist/core/context-budget-headroom.js +35 -0
  42. package/dist/core/context-budget-headroom.js.map +1 -1
  43. package/dist/core/context-budget-v2-input-validation.d.ts +4 -0
  44. package/dist/core/context-budget-v2-input-validation.d.ts.map +1 -0
  45. package/dist/core/context-budget-v2-input-validation.js +79 -0
  46. package/dist/core/context-budget-v2-input-validation.js.map +1 -0
  47. package/dist/core/context-budget-v2-observability.d.ts +15 -0
  48. package/dist/core/context-budget-v2-observability.d.ts.map +1 -0
  49. package/dist/core/context-budget-v2-observability.js +35 -0
  50. package/dist/core/context-budget-v2-observability.js.map +1 -0
  51. package/dist/core/context-budget-v2-planned-items.d.ts +5 -0
  52. package/dist/core/context-budget-v2-planned-items.d.ts.map +1 -0
  53. package/dist/core/context-budget-v2-planned-items.js +40 -0
  54. package/dist/core/context-budget-v2-planned-items.js.map +1 -0
  55. package/dist/core/context-budget-v2-planner.d.ts.map +1 -1
  56. package/dist/core/context-budget-v2-planner.js +57 -38
  57. package/dist/core/context-budget-v2-planner.js.map +1 -1
  58. package/dist/core/context-budget-v2-selection.d.ts +19 -3
  59. package/dist/core/context-budget-v2-selection.d.ts.map +1 -1
  60. package/dist/core/context-budget-v2-selection.js +35 -57
  61. package/dist/core/context-budget-v2-selection.js.map +1 -1
  62. package/dist/core/context-budget-v2-tiers.d.ts.map +1 -1
  63. package/dist/core/context-budget-v2-tiers.js +9 -42
  64. package/dist/core/context-budget-v2-tiers.js.map +1 -1
  65. package/dist/core/context-budget-v2-types.d.ts +8 -1
  66. package/dist/core/context-budget-v2-types.d.ts.map +1 -1
  67. package/dist/core/context-budget-v2-types.js.map +1 -1
  68. package/dist/core/devin-harness-dispatch.d.ts +12 -0
  69. package/dist/core/devin-harness-dispatch.d.ts.map +1 -0
  70. package/dist/core/devin-harness-dispatch.js +12 -0
  71. package/dist/core/devin-harness-dispatch.js.map +1 -0
  72. package/dist/core/devin-harness.d.ts +53 -0
  73. package/dist/core/devin-harness.d.ts.map +1 -0
  74. package/dist/core/devin-harness.js +112 -0
  75. package/dist/core/devin-harness.js.map +1 -0
  76. package/dist/core/domain-dispatch.d.ts +4 -1
  77. package/dist/core/domain-dispatch.d.ts.map +1 -1
  78. package/dist/core/domain-dispatch.js +5 -0
  79. package/dist/core/domain-dispatch.js.map +1 -1
  80. package/dist/core/domain-loadouts-provider-harness.d.ts +12 -0
  81. package/dist/core/domain-loadouts-provider-harness.d.ts.map +1 -0
  82. package/dist/core/domain-loadouts-provider-harness.js +122 -0
  83. package/dist/core/domain-loadouts-provider-harness.js.map +1 -0
  84. package/dist/core/domain-loadouts.d.ts +2 -33
  85. package/dist/core/domain-loadouts.d.ts.map +1 -1
  86. package/dist/core/domain-loadouts.js +3 -56
  87. package/dist/core/domain-loadouts.js.map +1 -1
  88. package/dist/core/domain-profile.d.ts +40 -0
  89. package/dist/core/domain-profile.d.ts.map +1 -0
  90. package/dist/core/domain-profile.js +7 -0
  91. package/dist/core/domain-profile.js.map +1 -0
  92. package/dist/core/extensions/bundled-virtual-modules.d.ts +15 -0
  93. package/dist/core/extensions/bundled-virtual-modules.d.ts.map +1 -0
  94. package/dist/core/extensions/bundled-virtual-modules.js +63 -0
  95. package/dist/core/extensions/bundled-virtual-modules.js.map +1 -0
  96. package/dist/core/extensions/loader.d.ts.map +1 -1
  97. package/dist/core/extensions/loader.js +2 -42
  98. package/dist/core/extensions/loader.js.map +1 -1
  99. package/dist/core/extensions/runner.d.ts +1 -0
  100. package/dist/core/extensions/runner.d.ts.map +1 -1
  101. package/dist/core/extensions/runner.js +6 -0
  102. package/dist/core/extensions/runner.js.map +1 -1
  103. package/dist/core/extensions/types.d.ts +10 -0
  104. package/dist/core/extensions/types.d.ts.map +1 -1
  105. package/dist/core/extensions/types.js.map +1 -1
  106. package/dist/core/grok-harness-dispatch.d.ts +6 -20
  107. package/dist/core/grok-harness-dispatch.d.ts.map +1 -1
  108. package/dist/core/grok-harness-dispatch.js +6 -55
  109. package/dist/core/grok-harness-dispatch.js.map +1 -1
  110. package/dist/core/grok-harness.d.ts +7 -9
  111. package/dist/core/grok-harness.d.ts.map +1 -1
  112. package/dist/core/grok-harness.js +9 -23
  113. package/dist/core/grok-harness.js.map +1 -1
  114. package/dist/core/harness-skills.d.ts +20 -0
  115. package/dist/core/harness-skills.d.ts.map +1 -0
  116. package/dist/core/harness-skills.js +36 -0
  117. package/dist/core/harness-skills.js.map +1 -0
  118. package/dist/core/index.d.ts +2 -1
  119. package/dist/core/index.d.ts.map +1 -1
  120. package/dist/core/index.js +2 -1
  121. package/dist/core/index.js.map +1 -1
  122. package/dist/core/loadout-runtime-state.d.ts +25 -0
  123. package/dist/core/loadout-runtime-state.d.ts.map +1 -0
  124. package/dist/core/loadout-runtime-state.js +39 -0
  125. package/dist/core/loadout-runtime-state.js.map +1 -0
  126. package/dist/core/loadout-runtime.d.ts +4 -20
  127. package/dist/core/loadout-runtime.d.ts.map +1 -1
  128. package/dist/core/loadout-runtime.js +6 -13
  129. package/dist/core/loadout-runtime.js.map +1 -1
  130. package/dist/core/mcp/client.d.ts +27 -6
  131. package/dist/core/mcp/client.d.ts.map +1 -1
  132. package/dist/core/mcp/client.js +80 -20
  133. package/dist/core/mcp/client.js.map +1 -1
  134. package/dist/core/mcp/manager.d.ts +10 -1
  135. package/dist/core/mcp/manager.d.ts.map +1 -1
  136. package/dist/core/mcp/manager.js +68 -9
  137. package/dist/core/mcp/manager.js.map +1 -1
  138. package/dist/core/mcp/protocol.d.ts +9 -3
  139. package/dist/core/mcp/protocol.d.ts.map +1 -1
  140. package/dist/core/mcp/protocol.js +61 -15
  141. package/dist/core/mcp/protocol.js.map +1 -1
  142. package/dist/core/mcp/stdio-transport.d.ts +12 -1
  143. package/dist/core/mcp/stdio-transport.d.ts.map +1 -1
  144. package/dist/core/mcp/stdio-transport.js +30 -2
  145. package/dist/core/mcp/stdio-transport.js.map +1 -1
  146. package/dist/core/mcp-descriptor-injection.d.ts +26 -0
  147. package/dist/core/mcp-descriptor-injection.d.ts.map +1 -0
  148. package/dist/core/mcp-descriptor-injection.js +25 -0
  149. package/dist/core/mcp-descriptor-injection.js.map +1 -0
  150. package/dist/core/mcp-public-presets.d.ts +1 -3
  151. package/dist/core/mcp-public-presets.d.ts.map +1 -1
  152. package/dist/core/mcp-public-presets.js +3 -15
  153. package/dist/core/mcp-public-presets.js.map +1 -1
  154. package/dist/core/model-registry.d.ts.map +1 -1
  155. package/dist/core/model-registry.js +24 -3
  156. package/dist/core/model-registry.js.map +1 -1
  157. package/dist/core/model-resolver.d.ts +1 -41
  158. package/dist/core/model-resolver.d.ts.map +1 -1
  159. package/dist/core/model-resolver.js +2 -49
  160. package/dist/core/model-resolver.js.map +1 -1
  161. package/dist/core/neo/catalog.d.ts +20 -0
  162. package/dist/core/neo/catalog.d.ts.map +1 -0
  163. package/dist/core/neo/catalog.js +50 -0
  164. package/dist/core/neo/catalog.js.map +1 -0
  165. package/dist/core/neo/setup.d.ts +3 -0
  166. package/dist/core/neo/setup.d.ts.map +1 -0
  167. package/dist/core/neo/setup.js +47 -0
  168. package/dist/core/neo/setup.js.map +1 -0
  169. package/dist/core/provider-default-models.d.ts +43 -0
  170. package/dist/core/provider-default-models.d.ts.map +1 -0
  171. package/dist/core/provider-default-models.js +51 -0
  172. package/dist/core/provider-default-models.js.map +1 -0
  173. package/dist/core/provider-display-names.d.ts.map +1 -1
  174. package/dist/core/provider-display-names.js +2 -0
  175. package/dist/core/provider-display-names.js.map +1 -1
  176. package/dist/core/provider-error-classification.d.ts +57 -0
  177. package/dist/core/provider-error-classification.d.ts.map +1 -0
  178. package/dist/core/provider-error-classification.js +103 -0
  179. package/dist/core/provider-error-classification.js.map +1 -0
  180. package/dist/core/provider-harness-dispatch.d.ts +63 -0
  181. package/dist/core/provider-harness-dispatch.d.ts.map +1 -0
  182. package/dist/core/provider-harness-dispatch.js +60 -0
  183. package/dist/core/provider-harness-dispatch.js.map +1 -0
  184. package/dist/core/provider-resilience.d.ts +10 -0
  185. package/dist/core/provider-resilience.d.ts.map +1 -1
  186. package/dist/core/provider-resilience.js +36 -3
  187. package/dist/core/provider-resilience.js.map +1 -1
  188. package/dist/core/provider-usage-commandcode.d.ts +9 -0
  189. package/dist/core/provider-usage-commandcode.d.ts.map +1 -0
  190. package/dist/core/provider-usage-commandcode.js +198 -0
  191. package/dist/core/provider-usage-commandcode.js.map +1 -0
  192. package/dist/core/provider-usage-devin.d.ts +18 -0
  193. package/dist/core/provider-usage-devin.d.ts.map +1 -0
  194. package/dist/core/provider-usage-devin.js +71 -0
  195. package/dist/core/provider-usage-devin.js.map +1 -0
  196. package/dist/core/provider-usage-text.d.ts +5 -0
  197. package/dist/core/provider-usage-text.d.ts.map +1 -0
  198. package/dist/core/provider-usage-text.js +15 -0
  199. package/dist/core/provider-usage-text.js.map +1 -0
  200. package/dist/core/provider-usage-types.d.ts +2 -1
  201. package/dist/core/provider-usage-types.d.ts.map +1 -1
  202. package/dist/core/provider-usage-types.js.map +1 -1
  203. package/dist/core/provider-usage.d.ts +1 -2
  204. package/dist/core/provider-usage.d.ts.map +1 -1
  205. package/dist/core/provider-usage.js +18 -7
  206. package/dist/core/provider-usage.js.map +1 -1
  207. package/dist/core/resource-admission.d.ts +36 -0
  208. package/dist/core/resource-admission.d.ts.map +1 -1
  209. package/dist/core/resource-admission.js +59 -0
  210. package/dist/core/resource-admission.js.map +1 -1
  211. package/dist/core/resource-loader.d.ts.map +1 -1
  212. package/dist/core/resource-loader.js +2 -2
  213. package/dist/core/resource-loader.js.map +1 -1
  214. package/dist/core/run-execution-api.d.ts +1 -1
  215. package/dist/core/run-execution-api.d.ts.map +1 -1
  216. package/dist/core/run-execution-api.js.map +1 -1
  217. package/dist/core/run-usage-ledger.d.ts +47 -0
  218. package/dist/core/run-usage-ledger.d.ts.map +1 -0
  219. package/dist/core/run-usage-ledger.js +162 -0
  220. package/dist/core/run-usage-ledger.js.map +1 -0
  221. package/dist/core/run-usage-operation.d.ts +8 -0
  222. package/dist/core/run-usage-operation.d.ts.map +1 -0
  223. package/dist/core/run-usage-operation.js +12 -0
  224. package/dist/core/run-usage-operation.js.map +1 -0
  225. package/dist/core/sdk.d.ts.map +1 -1
  226. package/dist/core/sdk.js +20 -16
  227. package/dist/core/sdk.js.map +1 -1
  228. package/dist/core/session-failure-cause.d.ts.map +1 -1
  229. package/dist/core/session-failure-cause.js +14 -5
  230. package/dist/core/session-failure-cause.js.map +1 -1
  231. package/dist/core/session-prompt-lifecycle.d.ts +6 -0
  232. package/dist/core/session-prompt-lifecycle.d.ts.map +1 -1
  233. package/dist/core/session-prompt-lifecycle.js +47 -2
  234. package/dist/core/session-prompt-lifecycle.js.map +1 -1
  235. package/dist/core/session-termination.d.ts.map +1 -1
  236. package/dist/core/session-termination.js +4 -2
  237. package/dist/core/session-termination.js.map +1 -1
  238. package/dist/core/subagent-lane-authority.d.ts +28 -0
  239. package/dist/core/subagent-lane-authority.d.ts.map +1 -0
  240. package/dist/core/subagent-lane-authority.js +119 -0
  241. package/dist/core/subagent-lane-authority.js.map +1 -0
  242. package/dist/core/subagent-lane-contract.d.ts +114 -0
  243. package/dist/core/subagent-lane-contract.d.ts.map +1 -0
  244. package/dist/core/subagent-lane-contract.js +18 -0
  245. package/dist/core/subagent-lane-contract.js.map +1 -0
  246. package/dist/core/subagent-lane-launcher.d.ts +3 -6
  247. package/dist/core/subagent-lane-launcher.d.ts.map +1 -1
  248. package/dist/core/subagent-lane-launcher.js +53 -12
  249. package/dist/core/subagent-lane-launcher.js.map +1 -1
  250. package/dist/core/subagent-orchestration.d.ts +4 -17
  251. package/dist/core/subagent-orchestration.d.ts.map +1 -1
  252. package/dist/core/subagent-orchestration.js.map +1 -1
  253. package/dist/core/verified-run/broker.d.ts.map +1 -1
  254. package/dist/core/verified-run/broker.js +4 -1
  255. package/dist/core/verified-run/broker.js.map +1 -1
  256. package/dist/core/verified-run/dag-phase.d.ts +1 -1
  257. package/dist/core/verified-run/dag-phase.d.ts.map +1 -1
  258. package/dist/core/verified-run/dag-phase.js +46 -8
  259. package/dist/core/verified-run/dag-phase.js.map +1 -1
  260. package/dist/core/verified-run/dag-projection.d.ts.map +1 -1
  261. package/dist/core/verified-run/dag-projection.js +25 -12
  262. package/dist/core/verified-run/dag-projection.js.map +1 -1
  263. package/dist/core/verified-run/dag-types.d.ts +11 -0
  264. package/dist/core/verified-run/dag-types.d.ts.map +1 -1
  265. package/dist/core/verified-run/dag-types.js.map +1 -1
  266. package/dist/core/verified-run/event-parser.d.ts.map +1 -1
  267. package/dist/core/verified-run/event-parser.js +1 -0
  268. package/dist/core/verified-run/event-parser.js.map +1 -1
  269. package/dist/core/verified-run/owned-execution.d.ts +1 -0
  270. package/dist/core/verified-run/owned-execution.d.ts.map +1 -1
  271. package/dist/core/verified-run/owned-execution.js +7 -1
  272. package/dist/core/verified-run/owned-execution.js.map +1 -1
  273. package/dist/core/verified-run/process-projection.d.ts +13 -0
  274. package/dist/core/verified-run/process-projection.d.ts.map +1 -0
  275. package/dist/core/verified-run/process-projection.js +82 -0
  276. package/dist/core/verified-run/process-projection.js.map +1 -0
  277. package/dist/core/verified-run/projection.d.ts.map +1 -1
  278. package/dist/core/verified-run/projection.js +11 -55
  279. package/dist/core/verified-run/projection.js.map +1 -1
  280. package/dist/core/verified-run/run-types.d.ts +1 -0
  281. package/dist/core/verified-run/run-types.d.ts.map +1 -1
  282. package/dist/core/verified-run/run-types.js.map +1 -1
  283. package/dist/core/verified-run/task-execution.d.ts +9 -0
  284. package/dist/core/verified-run/task-execution.d.ts.map +1 -0
  285. package/dist/core/verified-run/task-execution.js +29 -0
  286. package/dist/core/verified-run/task-execution.js.map +1 -0
  287. package/dist/core/verified-run/writer-projection.d.ts.map +1 -1
  288. package/dist/core/verified-run/writer-projection.js +6 -5
  289. package/dist/core/verified-run/writer-projection.js.map +1 -1
  290. package/dist/core/workload-permit-pool.d.ts +1 -1
  291. package/dist/core/workload-permit-pool.d.ts.map +1 -1
  292. package/dist/core/workload-permit-pool.js +3 -0
  293. package/dist/core/workload-permit-pool.js.map +1 -1
  294. package/dist/guardrails/strict-evidence-approval-adapter.d.ts +34 -0
  295. package/dist/guardrails/strict-evidence-approval-adapter.d.ts.map +1 -0
  296. package/dist/guardrails/strict-evidence-approval-adapter.js +70 -0
  297. package/dist/guardrails/strict-evidence-approval-adapter.js.map +1 -0
  298. package/dist/index.d.ts +3 -0
  299. package/dist/index.d.ts.map +1 -1
  300. package/dist/index.js +1 -0
  301. package/dist/index.js.map +1 -1
  302. package/dist/main.d.ts.map +1 -1
  303. package/dist/main.js +11 -18
  304. package/dist/main.js.map +1 -1
  305. package/dist/modes/acp/acp-agent.d.ts +24 -0
  306. package/dist/modes/acp/acp-agent.d.ts.map +1 -0
  307. package/dist/modes/acp/acp-agent.js +133 -0
  308. package/dist/modes/acp/acp-agent.js.map +1 -0
  309. package/dist/modes/acp/acp-mode.d.ts +4 -0
  310. package/dist/modes/acp/acp-mode.d.ts.map +1 -0
  311. package/dist/modes/acp/acp-mode.js +34 -0
  312. package/dist/modes/acp/acp-mode.js.map +1 -0
  313. package/dist/modes/acp/acp-session.d.ts +4 -0
  314. package/dist/modes/acp/acp-session.d.ts.map +1 -0
  315. package/dist/modes/acp/acp-session.js +76 -0
  316. package/dist/modes/acp/acp-session.js.map +1 -0
  317. package/dist/modes/acp/acp-transport.d.ts +5 -0
  318. package/dist/modes/acp/acp-transport.d.ts.map +1 -0
  319. package/dist/modes/acp/acp-transport.js +90 -0
  320. package/dist/modes/acp/acp-transport.js.map +1 -0
  321. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  322. package/dist/modes/interactive/interactive-mode.js +3 -5
  323. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  324. package/dist/modes/interactive/resource-description.d.ts +3 -0
  325. package/dist/modes/interactive/resource-description.d.ts.map +1 -0
  326. package/dist/modes/interactive/resource-description.js +15 -0
  327. package/dist/modes/interactive/resource-description.js.map +1 -0
  328. package/docs/audit-revalidation-2026-09-17.md +77 -0
  329. package/docs/devin-harness.md +150 -0
  330. package/docs/docs.json +4 -0
  331. package/docs/ecraf-normalization.md +83 -0
  332. package/docs/environment-variables.md +2 -1
  333. package/docs/index.md +1 -0
  334. package/docs/loadout-domains/README.md +2 -1
  335. package/docs/loadout-domains/devin-harness.md +72 -0
  336. package/docs/metrics.md +57 -16
  337. package/docs/model-catalog-refresh.md +51 -1
  338. package/docs/models.md +1 -1
  339. package/docs/neo.md +134 -0
  340. package/docs/providers.md +143 -1
  341. package/docs/release-audit-0.98.5.md +17 -0
  342. package/docs/release-audit-0.99.0.md +68 -0
  343. package/docs/run-protocol.md +4 -3
  344. package/docs/run-usage-ledger.md +56 -0
  345. package/docs/runtime-algorithms.md +48 -1
  346. package/docs/sdk.md +9 -5
  347. package/docs/settings.md +1 -1
  348. package/docs/skills.md +1 -1
  349. package/docs/startup-resource-labels-testing.md +47 -0
  350. package/docs/tb21-audit.md +18 -4
  351. package/docs/usage.md +7 -1
  352. package/docs/verified-run-remaining-design.md +881 -0
  353. package/docs/verified-run-testing.md +91 -0
  354. package/docs/verified-run.md +30 -9
  355. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  356. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  357. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  358. package/examples/extensions/gondolin/package-lock.json +2 -2
  359. package/examples/extensions/gondolin/package.json +1 -1
  360. package/examples/extensions/sandbox/package-lock.json +2 -2
  361. package/examples/extensions/sandbox/package.json +1 -1
  362. package/examples/extensions/subagent/adaptive-agent-runtime.ts +14 -1
  363. package/examples/extensions/subagent/graph-result.ts +48 -0
  364. package/examples/extensions/subagent/index.ts +246 -111
  365. package/examples/extensions/subagent/managed-process-tree.ts +42 -0
  366. package/examples/extensions/subagent/managed-process.test.ts +24 -0
  367. package/examples/extensions/subagent/managed-process.ts +93 -112
  368. package/examples/extensions/subagent/subagent-runtime-types.ts +17 -1
  369. package/examples/extensions/subagent/subagent-stream.ts +161 -0
  370. package/examples/extensions/terminal-browser/README.md +54 -0
  371. package/examples/extensions/terminal-browser/bridge-protocol.ts +29 -0
  372. package/examples/extensions/terminal-browser/bridge.ts +219 -0
  373. package/examples/extensions/terminal-browser/browser-surface.ts +288 -0
  374. package/examples/extensions/terminal-browser/index.ts +215 -0
  375. package/examples/extensions/terminal-browser/placeholders.ts +49 -0
  376. package/examples/extensions/with-deps/package-lock.json +2 -2
  377. package/examples/extensions/with-deps/package.json +1 -1
  378. package/npm-shrinkwrap.json +18 -18
  379. package/package.json +8 -7
  380. package/resources/neo/skills/omk-browser/SKILL.md +32 -0
  381. package/resources/neo/skills/omk-code-review/SKILL.md +24 -0
  382. package/resources/neo/skills/omk-computeruse/SKILL.md +34 -0
  383. package/resources/neo/skills/omk-mcp-setup/SKILL.md +48 -0
  384. package/resources/neo/skills/omk-research/SKILL.md +24 -0
  385. package/resources/neo/skills/omk-site/SKILL.md +28 -0
@@ -0,0 +1,881 @@
1
+ # OMK Verified Run: 미구현 항목 상세 구현 설계
2
+
3
+ - 작성 기준일: 2026-09-13, Asia/Seoul
4
+ - 기준 소스: HEAD `5b767f25c7`. U1(bounded eager frontier)은 `decee7f157`로 커밋됐고, 이후 커밋은 verified-run 모듈을 바꾸지 않았다.
5
+ - 문서 성격: **구현 설계서**. 아래의 새 타입·필드·이벤트·명령·모듈은 별도 표시가 없어도 제안이며, 현재 OMK에 존재하는 API로 해석하지 않는다.
6
+ - 선행 자료: 추적하지 않는 내부 작업 문서의 검증 실행 설계와 하네스 비교 결과. 이 문서는 그중 아직 닫히지 않은 항목만 다루며, 그 문서들에 링크하지 않는다.
7
+ - 검증 범위: 이 문서는 코드를 바꾸지 않았다. 각 절의 "현재 소스 근거"는 이번에 직접 읽은 파일·기호이며, "제안"은 그 근거에 결합하는 변경이다. 수식은 §14의 방법으로 컴파일 검사했다.
8
+
9
+ ## 0. 범위와 순서
10
+
11
+ 이전 대조에서 미구현 또는 미해결로 남은 항목은 다음 아홉 개다. 각 항목은 독립적으로 검토·되돌리기 가능한 단위로 나눈다.
12
+
13
+ | 번호 | 항목 | 현재 상태 | 이 문서의 절 | 선행 조건 |
14
+ | --- | --- | --- | --- | --- |
15
+ | U1 | 병렬 frontier의 커밋 | 완료 (`decee7f157`) | §2 | 없음 |
16
+ | U2 | 검증 조건부 DAG 의존성(`after_verification` edge) | 미구현 | §3 | U1 |
17
+ | U3 | 계획 수정(amendment)과 변경 계약의 결과 adoption | 미구현 | §4 | U2 |
18
+ | U4 | 실제 모델 adapter를 사용하는 verified-run | 미구현 | §5 | 없음(U1과 독립) |
19
+ | U5 | 적용 승인·CAS(`run apply`) | 미구현 | §6 | 없음(U1과 독립) |
20
+ | U6 | 원격 취소(`run cancel`) | 미구현 | §7 | 없음 |
21
+ | U7 | artifact GC | 미구현 | §8 | U5 |
22
+ | U8 | 필요한 MCP 서버만 연결(loadout) | 미해결 | §9 | 없음 |
23
+ | U9 | 도구 호출 frontier(레벨 장벽 대체) | 미해결 | §10 | 없음 |
24
+ | U10 | TUI/RPC 제어 표면 | 미구현 | §11 | U5, U6 |
25
+ | U11 | 전체 crash window 복구 | 부분 구현 | §12 | 없음 |
26
+
27
+ 권장 구현 순서는 `U1 → U5 → U6 → U4 → U2 → U8 → U11 → U3 → U7 → U10 → U9`다. 이유는 §13에 있다. 동일 조건 경쟁 비교(설계서 §18)는 이 문서의 구현 범위가 아니라 측정 범위이며, 여기서는 다루지 않는다.
28
+
29
+ ## 1. 공통 불변식
30
+
31
+ 모든 항목은 아래 불변식을 유지해야 한다. 각 항목의 수용 시험에는 이 불변식의 회귀 검사를 포함한다.
32
+
33
+ ### 1.1 상태 전이의 결정론
34
+
35
+ 현재 `readRunJournal()`(`packages/coding-agent/src/core/verified-run/journal.ts`)은 v2 레코드를 hash chain으로 검증하고 `projectRun(events)`로 상태를 재생한다. 새 이벤트를 추가해도 다음이 유지되어야 한다.
36
+
37
+ $$
38
+ s_{k+1} = \delta(s_k, e_{k+1}), \qquad
39
+ \forall k:\ \operatorname{hash}(r_{k+1}) = H(\text{version}, k+1, g_{k+1}, \operatorname{hash}(r_k), e_{k+1})
40
+ $$
41
+
42
+ 여기서 $g_{k+1}$은 세대이며, 현재 구현은 `resumed`, `writer_restarted`, `tasks_retried` 이벤트에서만 $g$를 1 증가시킨다. 이 문서가 추가하는 세대 증가 이벤트는 §4의 `plan_amended` 하나뿐이며, 그 밖의 새 이벤트는 세대를 바꾸지 않는다.
43
+
44
+ ### 1.2 승인은 JSON 필드가 아니다
45
+
46
+ `RunCoordinator.start/resume/restartWriter/retryTasks`는 모두 `VerifiedRunApproval.approvedContractDigest`를 신뢰하는 host 호출로 받는다. 새 명령(`apply`, `cancel`, `amend`)도 같은 형태를 따른다. 계약이나 명령 JSON 안의 `approved`, `trusted`, `verified` 필드는 `runObject()`의 허용 키 목록에 없으므로 parser가 거부한다. 이 규칙을 완화하는 변경은 없다.
47
+
48
+ ### 1.3 세대 fencing
49
+
50
+ $$
51
+ \operatorname{Accept}(e) \Rightarrow
52
+ e.\text{generation} = \operatorname{CurrentGeneration}(\text{run})
53
+ \ \land\
54
+ e.\text{revision} = \operatorname{CurrentRevision}(\text{run})
55
+ $$
56
+
57
+ `recovery-command.ts`의 `commandDisposition()`은 `expectedRevision`/`expectedGeneration`을 현재 상태와 정확히 비교하고, 같은 `commandId`의 재요청은 `duplicate`로 분류해 재실행하지 않는다. 새 명령은 모두 `withRecoveryLease()`를 통과한다.
58
+
59
+ ### 1.4 예산은 재부여되지 않는다
60
+
61
+ `RecoveryBudget`(`recovery-clock.ts`)은 boot ID와 boot-relative `startedMs`, `workDeadlineMs`, `verifyCapMs`, `cleanupDeadlineMs`를 고정한다. 어떤 새 명령도 이 값을 갱신하지 않는다. 남은 시간은 다음으로만 계산한다.
62
+
63
+ $$
64
+ T_{\text{remaining}}(t) = \max\{0,\ D_{\text{phase}} - t\},
65
+ \qquad t = \text{boot-relative now},\ \text{same boot ID}
66
+ $$
67
+
68
+ boot ID가 다르거나 단조 시계가 줄어들면 `remainingRunTime()`이 거부한다. 이 거부를 우회하는 경로를 만들지 않는다.
69
+
70
+ ### 1.5 자식 실행의 회수
71
+
72
+ `dag-phase.ts`의 `executeDag()`는 `finally`에서 `stop.abort()` 후 `Promise.all(inFlight.values())`로 시작된 모든 작업을 기다린다. 새로 추가하는 모든 병렬 경로는 같은 계약을 따른다: **시작한 promise를 모두 기다리기 전에는 반환하거나 throw하지 않는다.**
73
+
74
+ ## 2. U1: 병렬 frontier 커밋 단위 (완료)
75
+
76
+ 이 단위는 `decee7f157`로 `origin/main`에 커밋·푸시됐다. 아래는 그 커밋의 근거·범위·검사 기록이다.
77
+
78
+ ### 2.1 현재 소스 근거
79
+
80
+ - `packages/protocol/src/run-dag.ts`: `RunDagWriter.maxConcurrentTasks?: 1 | 2`, `parseRunDagWriter()`가 `hasConcurrency`일 때만 필드를 허용하고 `1`/`2` 외 값을 거부한다. 생략 시 필드를 덧붙이지 않아 예전 계약 digest를 보존한다.
81
+ - `packages/coding-agent/src/core/verified-run/dag-phase.ts`: `executeDag()`가 `limit = contract.writer.maxConcurrentTasks ?? 1`로 `inFlight` map을 관리하고 `Promise.race`로 완료를 회수한다.
82
+ - `dag-projection.ts`: `reduceDagEvent()`가 `task_started`에서 `running` 작업 수가 `maxConcurrentTasks ?? 1` 이상이면 `task_not_ready`로 거부한다.
83
+ - `dag-types.ts`: `RunTaskExecution = ready | running(executionId) | exited(executionId, failure)`.
84
+ - `run-types.ts`: `dispatch` 이벤트에 `taskId?: string`.
85
+ - 신규 모듈: `process-projection.ts`, `task-execution.ts`.
86
+ - 테스트: `verified-run-dag-frontier.test.ts`(2), `verified-run-dag-parallel-safety.test.ts`(5), `verified-run-dag-parallel-cli.test.ts`(1). 이번 세션에서 8개 모두 통과했다.
87
+
88
+ ### 2.2 커밋 범위
89
+
90
+ 다음 경로만 stage한다. 같은 작업 트리에 있는 Devin provider, 시작 리소스 표시 수정, ROADMAP 등 다른 작업의 변경은 포함하지 않는다.
91
+
92
+ ```text
93
+ packages/protocol/src/run-dag.ts
94
+ packages/protocol/test/run-dag-contract.test.ts
95
+ packages/coding-agent/src/core/verified-run/dag-phase.ts
96
+ packages/coding-agent/src/core/verified-run/dag-projection.ts
97
+ packages/coding-agent/src/core/verified-run/dag-types.ts
98
+ packages/coding-agent/src/core/verified-run/event-parser.ts
99
+ packages/coding-agent/src/core/verified-run/owned-execution.ts
100
+ packages/coding-agent/src/core/verified-run/projection.ts
101
+ packages/coding-agent/src/core/verified-run/run-types.ts
102
+ packages/coding-agent/src/core/verified-run/writer-projection.ts
103
+ packages/coding-agent/src/core/verified-run/process-projection.ts
104
+ packages/coding-agent/src/core/verified-run/task-execution.ts
105
+ packages/coding-agent/test/verified-run-dag-frontier.test.ts
106
+ packages/coding-agent/test/verified-run-dag-parallel-safety.test.ts
107
+ packages/coding-agent/test/verified-run-dag-parallel-cli.test.ts
108
+ packages/coding-agent/docs/verified-run.md
109
+ packages/coding-agent/docs/verified-run-testing.md
110
+ packages/coding-agent/docs/run-protocol.md
111
+ packages/protocol/README.md
112
+ ```
113
+
114
+ `run-dag-contract.test.ts`와 세 문서는 현재 작업 트리에서 이미 수정되어 있으므로 diff를 확인한 뒤 frontier 관련 hunk만 stage한다. 커밋 직전 staged diff 전체를 검토하고 위 목록 밖의 hunk가 있으면 중단한다.
115
+
116
+ ### 2.3 수용 조건
117
+
118
+ 커밋 전 다음을 실행했다. protocol 4개 파일 70개 통과, coding-agent 12개 파일 49개 중 48개 통과였고 남은 1개(`verified-run-cli.test.ts`의 `linux-command-v1` CLI 시나리오)는 부하 30/16 CPU에서 30초 timeout이었으며 단독 재실행에서 통과했다. `npm run check`는 pre-commit hook에서 전부 통과했다.
119
+
120
+ ```bash
121
+ (cd packages/protocol && node ../../node_modules/vitest/dist/cli.js --run \
122
+ test/run-contract.test.ts test/run-dag-contract.test.ts test/run-dag-properties.test.ts test/run-task-retry.test.ts)
123
+ (cd packages/coding-agent && node ../../node_modules/vitest/dist/cli.js --run --maxWorkers=2 \
124
+ test/verified-run-dag*.test.ts test/verified-run-crash.test.ts test/verified-run-cli.test.ts)
125
+ npm run check
126
+ ```
127
+
128
+ 커밋 메시지: `feat(runtime): 최대 2개 작업의 eager frontier 연결` (`decee7f157`).
129
+
130
+ ## 3. U2: 검증 조건부 DAG 의존성
131
+
132
+ ### 3.1 문제
133
+
134
+ 현재 `RunDagTask.dependsOn: readonly string[]`은 단일 의미다: 선행 작업의 **출력 checkpoint가 수락됨**(`status === "succeeded"`, 같은 세대). 설계서 §11.2의 세 가지 edge 조건 중 `after_artifact`만 구현되어 있고, `after_execution`(실패해도 진행)과 `after_verification`(선행 산출물이 특정 검사를 통과해야 진행)은 없다.
135
+
136
+ 현재 검사는 최종 통합 candidate에서만 실행된다(`verification-phase.ts`의 `verifyCandidate()`). 작업 단위 검사가 없으므로 "C는 A의 출력이 검사 `lint-a`를 통과했을 때만 시작한다"를 표현할 수 없다.
137
+
138
+ ### 3.2 계약 확장 (`packages/protocol/src/run-dag.ts`)
139
+
140
+ `dependsOn`의 원소를 문자열 또는 객체로 허용한다. 문자열은 기존 의미(`after_artifact`)를 그대로 유지해 예전 계약 digest를 바꾸지 않는다.
141
+
142
+ ```typescript
143
+ export type RunDagEdge =
144
+ | string
145
+ | { readonly kind: "after_artifact"; readonly taskId: string }
146
+ | {
147
+ readonly kind: "after_execution";
148
+ readonly taskId: string;
149
+ readonly allowedOutcomes: readonly ("succeeded" | "failed")[];
150
+ }
151
+ | { readonly kind: "after_verification"; readonly taskId: string; readonly claimIds: readonly string[] };
152
+
153
+ export interface RunDagTaskCheck {
154
+ readonly claimId: string;
155
+ readonly argv: readonly string[];
156
+ readonly stdout: string;
157
+ }
158
+ export interface RunDagTask {
159
+ readonly id: string;
160
+ readonly dependsOn: readonly RunDagEdge[];
161
+ readonly writablePaths: readonly string[];
162
+ readonly attempts: readonly (readonly string[])[];
163
+ /** 작업 출력 checkpoint에 대해 실행하는 작업 단위 검사. 최종 검사를 대체하지 않는다. */
164
+ readonly checks?: readonly RunDagTaskCheck[];
165
+ }
166
+ ```
167
+
168
+ parser 규칙(`parseRunDagWriter()` 확장):
169
+
170
+ 1. 문자열 edge는 `{kind: "after_artifact", taskId}`로 **정규화하지 않는다**. 정규화하면 digest가 바뀌므로, 정규화된 형태는 메모리 상의 파생 뷰(`normalizeEdge()`)로만 사용한다.
171
+ 2. `after_verification.claimIds`는 1–8개, 각 ID는 선행 작업의 `checks[].claimId`에 존재해야 한다. 없으면 `RunContractError("edge claim")`.
172
+ 3. `after_execution.allowedOutcomes`는 1–2개 중복 없는 값이다. `["failed"]`만 허용하는 edge는 "실패 후 정리"용이며 허용된다.
173
+ 4. `checks`가 있는 작업은 `checks`가 1–8개, `claimId`는 작업 내에서 유일하고 contract 최상위 `checks[].claimId`와도 겹치지 않는다(최종 attestation의 claim 공간과 분리). 겹치면 `RunContractError("duplicate claim")`.
174
+ 5. `checks` 필드가 없는 작업 객체는 `runObject(value, ["id","dependsOn","writablePaths","attempts"])`로 파싱하고, 있는 작업만 5-key 목록으로 파싱한다(`maxConcurrentTasks`와 같은 `hasConcurrency` 패턴). 생략 시 digest 보존.
175
+ 6. `orderRunDag()`와 `runDagAncestors()`는 `normalizeEdge(edge).taskId`로 그래프를 만든다. 순환 검사는 edge 종류와 무관하게 적용한다.
176
+ 7. `after_verification` edge의 선행 작업에 `checks`가 없으면 `RunContractError("edge claim")`.
177
+
178
+ ### 3.3 Ready 판정
179
+
180
+ 작업 $v$의 정규화된 edge 집합을 $E(v)$, 선행 작업 $u$의 현재 projection을 $\pi(u)$, 현재 세대를 $g$라 하자.
181
+
182
+ $$
183
+ \operatorname{Ready}(v) \iff
184
+ \pi(v).\text{status} = \text{pending}
185
+ \ \land\
186
+ \bigwedge_{\varepsilon \in E(v)} \operatorname{EdgeOk}(\varepsilon)
187
+ \ \land\
188
+ |\{u : \pi(u).\text{status} = \text{running}\}| < \text{maxConcurrentTasks}
189
+ $$
190
+
191
+ $$
192
+ \operatorname{EdgeOk}(\varepsilon) =
193
+ \begin{cases}
194
+ \pi(u).\text{status} = \text{succeeded} \land \pi(u).\text{generation} = g
195
+ & \varepsilon = \text{after\_artifact}(u) \\[4pt]
196
+ \pi(u).\text{status} \in \varepsilon.\text{allowedOutcomes} \land \pi(u).\text{generation} = g
197
+ & \varepsilon = \text{after\_execution}(u, \cdot) \\[4pt]
198
+ \pi(u).\text{status} = \text{succeeded} \land \pi(u).\text{generation} = g
199
+ \land \forall c \in \varepsilon.\text{claimIds}:\ \operatorname{TaskCheck}(u, c) = \text{passed}
200
+ & \varepsilon = \text{after\_verification}(u, \cdot)
201
+ \end{cases}
202
+ $$
203
+
204
+ `after_execution`으로 `failed`를 허용한 선행 작업이 있어도, **최종 candidate 합성은 `succeeded` 작업의 출력만 사용한다**. 실패 출력을 입력으로 쓰는 edge는 지원하지 않는다. `composeDagCandidate()`는 `runDagAncestors()` 결과 중 `status === "succeeded"`인 작업만 병합하며, `after_execution([failed])` edge의 선행 작업은 입력에서 제외된다. 이 제외는 `dag-candidates.ts`에 명시적 필터로 구현하고, 필터 결과가 비어 있어도 초기 `input_checkpoint`는 항상 포함된다.
205
+
206
+ ### 3.4 작업 단위 검사와 새 이벤트
207
+
208
+ `task_finished`가 `outputDigest !== null`로 수락된 직후, 해당 작업에 `checks`가 있으면 다음을 수행한다.
209
+
210
+ 1. 출력 checkpoint를 읽기 전용 디렉터리 `tasks/<id>-<attempt>-g<g>-check`로 `materializeCandidate()`한다.
211
+ 2. 각 check를 `executeRunCommand(journal, {role: "verifier", argv, workspace, deadline, claimId, taskId}, ...)`로 실행한다. `role: "verifier"`, `taskId` 지정은 기존 dispatch 이벤트의 필드로 표현 가능하며 새 role 값을 추가하지 않는다.
212
+ 3. 결과를 새 이벤트로 append한다.
213
+
214
+ ```typescript
215
+ | {
216
+ readonly kind: "task_checked";
217
+ readonly taskId: string;
218
+ readonly attempt: number;
219
+ readonly outputDigest: string;
220
+ readonly checks: readonly CheckObservation[]; // evidence-binding.ts의 기존 타입
221
+ readonly observedMs: number;
222
+ }
223
+ ```
224
+
225
+ reducer 규칙(`reduceDagEvent()` 확장):
226
+
227
+ - `task_checked`는 해당 작업이 `succeeded`이고 `outputDigest`가 일치하며 같은 세대일 때만 수락한다. 아니면 `integrity`.
228
+ - 각 `CheckObservation`의 `claimId`는 작업의 `checks[].claimId` 집합과 정확히 일치해야 한다(누락·중복·초과 거부).
229
+ - 통과 판정은 `closesRunClaims()`와 같은 규칙을 작업 검사에 적용하되, 별도 순수 함수 `closesTaskClaims(task, {output, environment, checks})`로 구현한다. 관측의 `sourceRoot`는 `outputDigest`, `environmentDigest`는 run의 `environmentDigest`다.
230
+ - projection에 작업별 `checkResults: ReadonlyMap<claimId, "passed" | "failed">`를 추가한다. `RunTaskProjection`의 `succeeded` 분기에 선택 필드 `checks?: readonly {claimId, passed}[]`로 둔다. `RunTaskCheckpoint`의 기존 형태를 바꾸지 않기 위해 `checks`가 없는 예전 checkpoint는 "검사 없음"으로 읽는다.
231
+
232
+ $$
233
+ \operatorname{TaskCheck}(u, c) = \text{passed} \iff
234
+ \exists\, o \in \operatorname{checks}(u):\
235
+ o.\text{claimId} = c \land o.\text{exitCode} = 0 \land o.\text{failure} = \varnothing
236
+ \land o.\text{stdoutDigest} = H(\text{expected}_c)
237
+ $$
238
+
239
+ ### 3.5 검사 실패의 의미
240
+
241
+ 작업 검사가 하나라도 실패하면 작업 상태는 `succeeded`로 유지되지만(출력은 유효한 checkpoint다), `after_verification` edge를 가진 후속 작업은 시작하지 않는다. 다른 edge 종류로 연결된 후속 작업은 시작할 수 있다. 실행 가능한 작업이 없고 미완료 작업이 남으면 기존 `tasks_paused`로 정지한다. `inspectTaskRecovery()`의 `retryableTaskIds`에는 **검사가 실패한 succeeded 작업도 포함**한다(attempt 여유가 있을 때). 재시도하면 새 attempt가 출력을 다시 만들고 검사를 다시 실행한다.
242
+
243
+ 작업 검사 통과는 **checkpoint 수락 조건이지 최종 verified가 아니다**. 최종 `verifyCandidate()`는 그대로 모든 작업이 고정된 뒤 통합 candidate에 대해 contract 최상위 `checks`를 실행한다. 작업 검사 receipt는 최종 attestation에 포함하지 않으며, `evidence()` 출력에 `taskChecks` 배열로 별도 노출한다.
244
+
245
+ ### 3.6 수용 시험
246
+
247
+ | ID | 시나리오 | 요구 결과 |
248
+ | --- | --- | --- |
249
+ | V01 | 문자열 edge만 있는 기존 계약 | digest 불변, 기존 테스트 전부 통과 |
250
+ | V02 | `after_verification` edge, 선행 검사 통과 | 후속 작업 시작, 최종 verified |
251
+ | V03 | `after_verification` edge, 선행 검사 실패 | 후속 작업 미시작, `tasks_paused`, `retryableTaskIds`에 선행 작업 포함 |
252
+ | V04 | `after_execution([failed])` 정리 작업 | 선행 실패 후 정리 작업 시작, 최종 candidate에 선행 실패 출력 미포함 |
253
+ | V05 | `task_checked`의 claimId 집합 불일치 | `integrity` 거부 |
254
+ | V06 | edge가 존재하지 않는 claimId 참조 | parser `RunContractError("edge claim")` |
255
+ | V07 | 작업 검사 통과 후 출력 blob 손상 | 재개·재시도 시 `task_checkpoint_mismatch` |
256
+ | V08 | 작업 검사 중 SIGKILL | 재시작 후 `task_checked` 없음 → 검사 재실행, 출력 재생성 없음 |
257
+ | V09 | 작업 검사가 출력을 수정 | 읽기 전용 mount로 실패, 검사 실패로 기록 |
258
+ | V10 | 병렬 frontier + 검증 edge 조합 | 검사 통과 즉시 후속 시작, 무관한 형제 대기 없음 |
259
+
260
+ property test: 5개 노드에서 가능한 순방향 DAG 1,024개 각각에 대해 edge 종류를 무작위 배정하고, Ready 집합이 §3.3의 수식을 독립 구현(집합 연산만 사용)과 일치함을 확인한다. 기존 `run-dag-properties.test.ts` 패턴을 확장한다.
261
+
262
+ ## 4. U3: 계획 수정(amendment)과 결과 adoption
263
+
264
+ ### 4.1 문제
265
+
266
+ 현재 그래프는 승인 후 불변이다(`verified-run.md` "그래프는 승인 후 불변입니다"). 계약 digest가 바뀌면 새 run이 필요하고, 이전 run의 성공 checkpoint는 재사용할 수 없다. 설계서 §12.3은 "재계획은 task 정의나 의존성 또는 수락 조건의 변경"이며 "재사용 자체가 새 계획에 결합된 명시적 adoption 사건이어야 한다"고 규정한다.
267
+
268
+ ### 4.2 명령 계약 (`packages/protocol/src/run-amend.ts`, 신규)
269
+
270
+ ```typescript
271
+ export interface RunAmendCommand {
272
+ readonly schemaVersion: typeof VERIFIED_COMMAND_VERSION;
273
+ readonly kind: "amend";
274
+ readonly runId: string;
275
+ readonly commandId: string;
276
+ readonly expectedRevision: number;
277
+ readonly expectedGeneration: number;
278
+ /** 현재 승인된 계약의 digest. 이 값이 현재 원장의 계약과 다르면 거부. */
279
+ readonly contractDigest: string;
280
+ /** 새 계약 전체. parser는 profile이 linux-command-dag-v1인지 확인. */
281
+ readonly amendedContract: RunContract;
282
+ /** 이전 세대에서 그대로 채택할 작업 ID. 빈 배열 허용. */
283
+ readonly adoptTaskIds: readonly string[];
284
+ }
285
+ ```
286
+
287
+ 승인은 `VerifiedRunApproval.approvedContractDigest`에 **새 계약의 digest**를 넣는 별도 host 호출이다. 이전 계약 digest로는 amend할 수 없다.
288
+
289
+ ### 4.3 안정 노드 계약(stable node contract)
290
+
291
+ 작업 $u$의 안정 계약 digest를 다음으로 정의한다.
292
+
293
+ $$
294
+ \sigma(u) = H\big(
295
+ u.\text{id},\ u.\text{writablePaths},\ u.\text{attempts},\ u.\text{checks},\
296
+ [\sigma(w) : w \in \operatorname{Anc}(u)]_{\text{sorted by id}},\
297
+ \text{budget},\ \text{environmentDigest}
298
+ \big)
299
+ $$
300
+
301
+ $\operatorname{Anc}(u)$는 `runDagAncestors()`의 결과다. 재귀 정의이므로 위상 순서로 계산하며, 순환은 parser가 이미 배제한다. `dependsOn`의 edge 종류는 $\sigma$에 **포함하지 않는다**: 같은 입력·명령·검사·조상이면 edge 의미가 바뀌어도 이미 만든 출력은 동일하기 때문이다. 대신 edge 변경은 Ready 재계산으로 반영된다.
302
+
303
+ adoption 조건:
304
+
305
+ $$
306
+ \operatorname{Adoptable}(u) \iff
307
+ u \in \text{adoptTaskIds}
308
+ \ \land\
309
+ \pi_{\text{old}}(u).\text{status} = \text{succeeded}
310
+ \ \land\
311
+ \sigma_{\text{old}}(u) = \sigma_{\text{new}}(u)
312
+ \ \land\
313
+ \operatorname{BlobIntact}\big(\pi_{\text{old}}(u).\text{inputDigest}\big)
314
+ \ \land\
315
+ \operatorname{BlobIntact}\big(\pi_{\text{old}}(u).\text{outputDigest}\big)
316
+ $$
317
+
318
+ $\operatorname{BlobIntact}(d)$는 manifest $d$와 그가 참조하는 모든 blob 파일이 존재하고 각 파일의 SHA-256이 manifest의 digest와 일치한다는 뜻이다. 현재 `loadCandidate()`가 수행하는 검사와 같다.
319
+
320
+ 하나라도 실패하면 명령 전체를 `adoption_mismatch`로 거부한다. 부분 adoption으로 조용히 진행하지 않는다.
321
+
322
+ ### 4.4 이벤트와 세대
323
+
324
+ ```typescript
325
+ | {
326
+ readonly kind: "plan_amended";
327
+ readonly command: RunAmendCommand;
328
+ readonly observedMs: number;
329
+ readonly reconciledExecutionIds: readonly string[];
330
+ readonly adopted: readonly RunTaskCheckpoint[];
331
+ readonly previousContractDigest: string;
332
+ }
333
+ ```
334
+
335
+ - `plan_amended`는 세대를 1 증가시킨다(`readRunJournal()`의 세대 증가 목록에 추가). `MAX_VERIFIED_RUN_GENERATIONS = 3`은 그대로 공유한다. 즉 resume/restart/retry/amend를 합쳐 최대 두 번이다.
336
+ - `revision`도 1 증가한다. 이후 모든 명령의 `expectedRevision`은 새 값이어야 한다.
337
+ - 이후 `projectRun()`은 `plan_amended` 이후의 이벤트를 **새 계약**으로 해석한다. 이를 위해 `WriterReduction.contract`를 가변 필드로 두고 `plan_amended` 처리 시 교체한다. `created` 이벤트의 계약은 원본으로 보존된다.
338
+ - `adopted` checkpoint는 새 세대에 `status: "succeeded"`, `generation: 새 세대`로 복사된다. 기존 `tasks_retried.adopted`와 같은 처리다.
339
+ - 활성 실행이 있으면(`activeExecutionIds.length > 0` 또는 `running` 작업) amend를 `writer_open`으로 거부한다. 설계서 §12.3 "active writer가 없는 안전한 경계에서만".
340
+ - candidate가 이미 고정된 run(`candidateDigest !== null`)은 amend할 수 없다(`candidate_frozen`). 검증 후 계획 변경은 새 run이다.
341
+
342
+ ### 4.5 evidence와 CLI
343
+
344
+ - `readRunEvidence()`는 `first.contract`(원본)가 아니라 **현재 유효 계약**으로 attestation을 검증해야 한다. `plan_amended` 레코드가 있으면 마지막 것의 `amendedContract`를 사용한다. attestation의 `contractDigest`는 현재 계약 digest이며, 원본 계약 digest는 `originalContractDigest` 필드로 별도 기록한다(attestation version 4로 올린다; v3 읽기는 유지).
345
+ - CLI: `omk run amend ID --execute --contract NEW.json --approve NEW_DIGEST --adopt ID[,ID...]|- --revision N --generation N --command-id ID`. `--adopt -`는 빈 adoption이다. `inspect ID --amend-preview --contract NEW.json`은 읽기 전용으로 `adoptable`, `stale`, `new` 작업 목록과 $\sigma$ 비교 결과를 반환한다.
346
+
347
+ ### 4.6 수용 시험
348
+
349
+ | ID | 시나리오 | 요구 결과 |
350
+ | --- | --- | --- |
351
+ | A01 | 작업 하나 명령 변경, 나머지 adoption | 변경 작업만 재실행, adoption 작업 출력 재사용, 최종 검사 새로 실행 |
352
+ | A02 | adoption 요청한 작업의 조상이 변경됨 | $\sigma$ 불일치 → `adoption_mismatch`, 원장 무변경 |
353
+ | A03 | 활성 실행 중 amend | `writer_open` 거부 |
354
+ | A04 | candidate 고정 후 amend | `candidate_frozen` 거부 |
355
+ | A05 | 세대 3에서 amend | `generation_limit` 거부 |
356
+ | A06 | 이전 계약 digest로 승인 | `approval` 거부 |
357
+ | A07 | adoption blob 손상 | `adoption_mismatch` |
358
+ | A08 | 같은 commandId 재요청 | 조회만, 재실행 없음 |
359
+ | A09 | edge 종류만 변경(명령·입력 동일) | adoption 허용, Ready 재계산 |
360
+ | A10 | amend 후 resume/retry | 새 revision·세대 기준으로만 수락 |
361
+
362
+ ## 5. U4: 실제 모델 adapter를 사용하는 verified-run
363
+
364
+ ### 5.1 문제
365
+
366
+ `linux-scripted-agent-v1`은 Faux provider로 승인된 step index만 선택하는 합성 응답이다(`scripted-writer.ts`). 실제 provider(OpenAI Codex, Anthropic 등)가 자연어 목표에서 도구 호출을 생성하는 경로는 없다. 설계서 §4는 이를 계약 스냅샷·권한 축소·지원 행렬로 다룬다.
367
+
368
+ ### 5.2 새 profile 계약
369
+
370
+ ```typescript
371
+ export interface RunLiveAgentWriter {
372
+ readonly kind: "live-agent";
373
+ readonly provider: string; // 기존 ModelContract.allowedProviders 원소와 동일 형식
374
+ readonly modelId: string; // 정확한 사용자 모델 ID
375
+ readonly thinkingLevel: "off" | "low" | "medium" | "high" | "max";
376
+ readonly maxRequests: number; // 1–64
377
+ readonly maxOutputTokens: number; // 1–200000
378
+ /** 승인된 도구 이름. 현재는 "verified_shell" 하나만 허용. */
379
+ readonly tools: readonly ["verified_shell"];
380
+ /** 도구가 실행할 수 있는 argv[0]의 절대 경로 allowlist. */
381
+ readonly allowedExecutables: readonly string[]; // 1–32, 각각 runAbsolutePath
382
+ }
383
+ // RunContract에 { profile: "linux-live-agent-v1"; writer: RunLiveAgentWriter } 분기 추가
384
+ ```
385
+
386
+ `tools`를 튜플 리터럴로 고정하는 이유: 이 단계에서는 도구 하나만 지원하며, parser가 다른 이름을 거부하도록 타입과 검사를 일치시킨다.
387
+
388
+ ### 5.3 실행 경계
389
+
390
+ 기존 `scripted-writer.ts`와 `session-port.ts`를 재사용한다. 차이는 다음 세 가지뿐이다.
391
+
392
+ 1. **모델**: Faux 대신 host가 `VerifiedRunRuntime.createSession()`에 주입한 실제 provider 세션을 사용한다. runtime port에 `resolveModel(provider, modelId)` 함수를 추가하고, 계약의 provider/modelId를 `ModelContract`(`packages/agent/src/run-model-contract.ts`)로 변환해 `createAgentSession({ modelContract })`에 전달한다. 즉 §4.2의 스냅샷은 **기존 `--model-contract` 경로를 그대로 사용**한다.
393
+ 2. **도구**: `verified_shell` 도구는 `{argv: string[]}`만 받고, `argv[0]`이 `allowedExecutables`에 없으면 거부한다. 실행은 `executeRunCommand(journal, {role: "writer", argv, workspace, deadline, claimId: null}, ...)`로 bwrap 안에서 이루어진다. 도구 결과는 stdout/stderr digest와 앞 32 KiB(기존 receipt redaction 규칙)만 모델에 돌려준다.
394
+ 3. **요청 예산**: `session.prompt(goal, { runBudget: { timeoutMs, maxRequests, maxConcurrentRequests: 1 } })`로 기존 공유 예산을 사용한다. `maxOutputTokens`는 `ModelContract.maxOutputTokens`로 전달한다.
395
+
396
+ 원장 이벤트는 기존 `writer_opened → model_request → dispatch/exited → writer_closed`를 그대로 사용한다. `model_request.requestId`는 core의 `provider_request.requestId`와 같은 값을 쓴다.
397
+
398
+ ### 5.4 지원 행렬과 거부 조건
399
+
400
+ 설계서 §4.4의 행렬을 계약 검사로 구현한다. `packages/agent/src/run-model-contract.ts`의 기존 검사에 더해, live profile은 다음을 **시작 전에** 확인하고 하나라도 `unknown`이면 `adapter_unsupported`로 거부한다.
401
+
402
+ | capability | 확인 방법 | 현재 근거 |
403
+ | --- | --- | --- |
404
+ | 전송 모델 ID 확인 | `openai-completions` payload hook의 model ID 검사 | `model-contract.md` "Covered paths" |
405
+ | 출력 상한 필드 확인 | 같은 hook의 `max_tokens`/`max_completion_tokens` 검사 | 동일 |
406
+ | 취소 전달 | `provider-request.ts`의 signal 전달 | `provider-request-boundary.test.ts` |
407
+ | 사용량 보고 | 최종 metadata의 usage 존재 | `session-run-budget.ts` |
408
+
409
+ Codex responses adapter는 `model-contract.md`에 "does not serialize maxTokens"로 명시되어 있으므로 출력 상한 확인이 `unknown`이다. 따라서 첫 지원 provider는 `openai-completions` 계열로 한정하고, 다른 adapter는 각각 payload hook 검증을 추가한 뒤 행렬에 등록한다. 행렬은 코드 상수(`LIVE_AGENT_CAPABILITIES`)로 두고 문서는 그것을 인용한다.
410
+
411
+ ### 5.5 비용·과금 경계
412
+
413
+ 이 profile은 **실제 과금을 발생시킨다**. 다음을 명시한다.
414
+
415
+ - `plan`은 여전히 네트워크를 쓰지 않는다. `start`만 provider를 호출한다.
416
+ - `maxRequests`는 논리 요청 수이며 HTTP 재시도 수가 아니다. adapter의 내부 재시도는 `maxRetries: 0`으로 요청하되, 이것이 provider의 내부 시도를 증명하지는 않는다(`model-contract.md` 한계 유지).
417
+ - 재개(`resume`)는 writer를 재실행하지 않으므로 추가 과금이 없다. `restart-writer`는 새 writer 실행이므로 **남은 `maxRequests` 안에서만** 다시 과금된다. 이전 요청 수는 환급하지 않는다.
418
+ - 테스트는 loopback HTTP 서버로 `openai-completions` wire를 흉내 낸다. 실제 provider 호출 테스트는 `LIVE_E2E=1`과 해당 provider 환경 변수가 있을 때만 `describe.skipIf`로 실행한다.
419
+
420
+ ### 5.6 수용 시험
421
+
422
+ | ID | 시나리오 | 요구 결과 |
423
+ | --- | --- | --- |
424
+ | L01 | loopback 모델이 허용 executable 호출 | bwrap 실행, 출력 digest 회신, candidate 고정, verified |
425
+ | L02 | 모델이 allowlist 밖 executable 요청 | 도구 거부, 모델에 오류 회신, 원장에 dispatch 없음 |
426
+ | L03 | 모델이 `maxRequests` 초과 | `model_request_limit`, writer 미완료, candidate 없음 |
427
+ | L04 | payload hook에서 model ID 불일치 | `provider_denied`, 네트워크 전송 0 |
428
+ | L05 | 출력 상한 확인 불가 adapter | `adapter_unsupported`로 start 거부 |
429
+ | L06 | writer 중 SIGKILL 후 restart-writer | 남은 요청 수로 재실행, 이전 요청 수 유지 |
430
+ | L07 | 모델 응답에 `verified: true` 텍스트 | 검사 결과에 영향 없음 |
431
+ | L08 | 도구 결과에 credential 문자열 | 모델 입력 전 redaction, receipt에도 미포함 |
432
+ | L09 | 취소 신호 | provider 요청 취소 전달, 활성 bwrap 회수, `cancelled` |
433
+ | L10 | 사용량 metadata 결측 | 예산 snapshot에 `usage_unknown`, 0으로 정산하지 않음 |
434
+
435
+ ## 6. U5: 적용 승인과 CAS (`run apply`)
436
+
437
+ ### 6.1 문제
438
+
439
+ 현재 `apply: "artifact-only"`만 허용된다. 검증된 candidate를 원본 workspace에 반영하는 명령이 없다. 설계서 §10.3: 적용은 기대 base를 명시하는 compare-and-swap이어야 하며, 검증한 바로 그 snapshot만 적용한다.
440
+
441
+ ### 6.2 명령 계약 (`packages/protocol/src/run-apply.ts`, 신규)
442
+
443
+ ```typescript
444
+ export interface RunApplyCommand {
445
+ readonly schemaVersion: typeof VERIFIED_COMMAND_VERSION;
446
+ readonly kind: "apply";
447
+ readonly runId: string;
448
+ readonly commandId: string;
449
+ readonly expectedRevision: number;
450
+ readonly expectedGeneration: number;
451
+ readonly contractDigest: string;
452
+ /** 검증된 candidate. 원장의 candidateDigest와 정확히 일치. */
453
+ readonly candidateDigest: string;
454
+ /** 적용 직전 원본 workspace가 가져야 하는 digest. 계약의 baseDigest와 같아야 한다. */
455
+ readonly expectedBaseDigest: string;
456
+ /** 적용 대상 절대 경로. 계약의 workspace.root와 같아야 한다. */
457
+ readonly targetRoot: string;
458
+ }
459
+ ```
460
+
461
+ `apply`는 `RunContract.apply`의 값을 `"artifact-only" | "managed-apply"`로 확장할 때만 허용한다. `"artifact-only"` 계약에는 `apply` 명령이 `apply_not_requested`로 거부된다. 계약 값 변경은 digest를 바꾸므로 새 승인이 필요하다.
462
+
463
+ ### 6.3 CAS 조건
464
+
465
+ $$
466
+ \operatorname{Apply}(h_c) \Rightarrow
467
+ \underbrace{\operatorname{capture}(\text{targetRoot}) = h_b}_{\text{base unchanged}}
468
+ \ \land\
469
+ \underbrace{h_c = \text{state.candidateDigest}}_{\text{same snapshot}}
470
+ \ \land\
471
+ \underbrace{\text{state.verification} = \text{verified}}_{\text{receipt valid}}
472
+ \ \land\
473
+ \underbrace{\operatorname{readRunEvidence}() \text{ succeeds}}_{\text{attestation intact}}
474
+ $$
475
+
476
+ 여기서 $h_b$ = `expectedBaseDigest` = `contract.workspace.baseDigest`. `captureCandidate(targetRoot, contract.budget)`는 `.git`/`.omk`를 제외한 전체 트리를 다시 읽으므로 사용자가 검증 중 파일을 바꿨으면 base가 달라져 `base_moved`로 거부한다.
477
+
478
+ ### 6.4 적용 알고리즘
479
+
480
+ 원자적 디렉터리 교체는 사용자가 편집 중인 임의 트리에서 보장할 수 없다(설계서 §10.3). 따라서 다음 순서로 **부분 적용을 감지 가능하게** 만든다.
481
+
482
+ 1. `withRecoveryLease()`로 단일 owner를 획득한다(다른 apply/resume과 직렬화).
483
+ 2. `apply_intent` 이벤트를 append/fsync한다: `{kind: "apply_intent", command, candidateDigest, baseDigest, plannedWrites: [{path, digest, mode} ...], plannedDeletes: [path ...]}`. `plannedWrites`는 candidate manifest와 base manifest의 차집합이다.
484
+ 3. base와 candidate 모두에 있고 digest가 같은 파일은 건드리지 않는다.
485
+ 4. 각 쓰기는 같은 디렉터리의 임시 파일(`.omk-apply-<runId>-<random>`)에 쓰고 fsync한 뒤 `rename`한다. 각 삭제는 `unlink`한다. 디렉터리 생성은 부모부터 순서대로 한다.
486
+ 5. 모든 쓰기·삭제 후 부모 디렉터리들을 fsync한다.
487
+ 6. `captureCandidate(targetRoot)`를 다시 실행해 `h_c`와 비교한다. 같으면 `applied` 이벤트, 다르면 `apply_diverged` 이벤트를 append한다. **어느 쪽이든 되돌리기(rollback)를 자동으로 수행하지 않는다.** 원본은 이미 `input_checkpoint` blob으로 보존되어 있으므로, 사용자는 `omk run artifact`로 base 파일을 회수할 수 있다.
488
+ 7. 4–6 사이에서 프로세스가 죽으면 재시작 시 `apply_intent`만 있고 `applied`/`apply_diverged`가 없는 상태다. `inspect --apply-recovery`는 현재 트리를 다시 capture해 (a) base와 같음 → "미적용", (b) candidate와 같음 → "적용 완료(미기록)", (c) 둘 다 아님 → "부분 적용"으로 분류만 하고, 후속 행동은 사용자 명령(`apply` 재요청 또는 수동)이다. 같은 `commandId`의 `apply` 재요청은 (a)에서만 실행을 계속하고, (b)에서는 `applied`를 기록하며, (c)에서는 `apply_partial`로 거부한다.
489
+
490
+ $$
491
+ \operatorname{Classify}(\text{tree}) =
492
+ \begin{cases}
493
+ \text{unapplied} & \operatorname{capture}(\text{tree}) = h_b \\
494
+ \text{applied\_unrecorded} & \operatorname{capture}(\text{tree}) = h_c \\
495
+ \text{partial} & \text{otherwise}
496
+ \end{cases}
497
+ $$
498
+
499
+ ### 6.5 지원 범위와 거부
500
+
501
+ - `targetRoot`는 계약의 `workspace.root`와 정확히 같아야 한다. 다른 경로로의 적용은 지원하지 않는다.
502
+ - symlink, hardlink, 특수 파일, 잘못된 UTF-8 이름은 candidate 단계에서 이미 거부되므로 적용 대상에도 없다.
503
+ - base 트리에 candidate manifest에 없는 새 파일이 생겼으면 base digest가 달라져 거부된다. 즉 "검증 후 사용자가 파일을 추가한" 상태에서는 적용할 수 없고, 사용자가 그 파일을 치우거나 새 run을 만들어야 한다. 이는 의도된 보수적 정책이다.
504
+ - Git ref 갱신, commit, index 변경은 하지 않는다. 적용 후 `git status`는 사용자 책임이다.
505
+
506
+ ### 6.6 수용 시험
507
+
508
+ | ID | 시나리오 | 요구 결과 |
509
+ | --- | --- | --- |
510
+ | P01 | verified candidate를 미변경 base에 적용 | 파일 반영, `applied`, 재capture = $h_c$ |
511
+ | P02 | 검증 후 base 파일 변경 | `base_moved`, 트리 무변경 |
512
+ | P03 | `artifact-only` 계약에 apply | `apply_not_requested` |
513
+ | P04 | 다른 candidate digest | `candidate_mismatch` |
514
+ | P05 | attestation 손상 | `integrity`, 트리 무변경 |
515
+ | P06 | 쓰기 도중 SIGKILL | `apply_intent`만 존재; `--apply-recovery`가 partial 분류; 같은 commandId 재요청은 `apply_partial` |
516
+ | P07 | 적용 완료 후 `applied` 기록 전 SIGKILL | recovery가 `applied_unrecorded`; 재요청이 `applied` 기록 |
517
+ | P08 | 삭제가 포함된 candidate | 파일 삭제 반영, 빈 디렉터리 처리 일치 |
518
+ | P09 | 같은 commandId 두 번 | 두 번째는 조회 |
519
+ | P10 | 다른 owner가 lease 보유 | `lease_held` 거부 |
520
+
521
+ ## 7. U6: 원격 취소 (`run cancel`)
522
+
523
+ ### 7.1 문제
524
+
525
+ 현재 취소는 실행 중인 supervisor 프로세스의 `AbortSignal`(CLI SIGINT/SIGTERM 또는 SDK signal)로만 전달된다. 다른 프로세스에서 실행 중인 run을 취소하는 명령이 없다.
526
+
527
+ ### 7.2 설계
528
+
529
+ 원장은 단일 writer(owner lease)이므로, 취소 요청자는 원장에 쓸 수 없다. 대신 **run 디렉터리 안의 별도 요청 파일**을 사용한다.
530
+
531
+ - 요청자: `omk run cancel ID --command-id ID [--state-dir DIR]`는 `runPath/cancel-requests/<commandId>.json`을 `publishObject()`로 원자적으로 생성한다. 내용은 `{schemaVersion, kind: "cancel", runId, commandId, requestedAtBootId, requestedAtMs}`. 이미 있으면 조회다. 원장은 건드리지 않는다.
532
+ - 소유자: `executeRunCommand()`가 dispatch 전에, 그리고 `executeSandbox()`의 stdin gate를 열기 직전에 `cancel-requests/` 디렉터리를 확인한다. 파일이 있으면 `cancel_observed` 이벤트를 append하고 `AbortController.abort()`로 기존 취소 경로에 합류한다. 확인 비용은 `readdirSync` 한 번이며 dispatch당 한 번이다.
533
+ - 실행 중인 bwrap 자식은 기존 취소 경로(SIGTERM → cleanup 기한 → SIGKILL, `quarantined` 판정)를 그대로 따른다. 취소 요청 파일이 있다고 소유자가 즉시 죽지는 않는다.
534
+ - 소유자가 없는 run(살아 있는 lease 없음)에 대한 cancel은 파일만 남기고 `no_owner`를 반환한다. 다음 `resume`/`retry-tasks`/`restart-writer`는 시작 전에 cancel 파일을 확인하고 `cancelled`로 거부하며, 사용자가 `--clear-cancel`로 파일을 제거해야 재개할 수 있다.
535
+
536
+ 취소 관측과 실제 종료는 다르다(설계서 §5.3). `cancel` 명령의 반환값은 `{requested: true, ownerAlive: boolean}`이며 종료를 보장하지 않는다. 종료 확인은 `inspect`의 `activeExecutionIds`와 `settlement`로 한다.
537
+
538
+ ### 7.3 수용 시험
539
+
540
+ | ID | 시나리오 | 요구 결과 |
541
+ | --- | --- | --- |
542
+ | C01 | 실행 중 다른 프로세스에서 cancel | 다음 dispatch 경계에서 `cancel_observed`, 활성 실행 회수, `cancelled` |
543
+ | C02 | 소유자 없는 run에 cancel | `no_owner`, 파일 생성, 이후 resume 거부 |
544
+ | C03 | `--clear-cancel` 후 resume | 정상 재개 |
545
+ | C04 | 같은 commandId 재요청 | 조회 |
546
+ | C05 | 취소 무시하는 자식 | cleanup 기한 후 `quarantined`, settled 아님 |
547
+
548
+ ## 8. U7: artifact GC
549
+
550
+ ### 8.1 문제
551
+
552
+ `verified-run.md`: "orphan artifact가 남을 수 있으며 GC는 없습니다." blobs, tasks/*, writer-N, candidate-N, attestations, check-receipts가 run 디렉터리에 누적된다.
553
+
554
+ ### 8.2 보존 규칙
555
+
556
+ run $r$의 도달 가능 집합 $R(r)$을 다음으로 정의한다.
557
+
558
+ $$
559
+ R(r) = \{\text{issuer.key}, \text{journal.v2.jsonl}\}
560
+ \ \cup\ \operatorname{Blobs}(\text{inputDigest})
561
+ \ \cup\ \bigcup_{u \in \text{tasks}} \operatorname{Blobs}(\pi(u).\text{inputDigest}) \cup \operatorname{Blobs}(\pi(u).\text{outputDigest})
562
+ \ \cup\ \operatorname{Blobs}(\text{candidateDigest})
563
+ \ \cup\ \{\text{attestations/receiptDigest.json}\}
564
+ \ \cup\ \operatorname{Receipts}(\text{receiptDigest})
565
+ $$
566
+
567
+ $\operatorname{Blobs}(d)$는 manifest $d$가 참조하는 모든 blob 파일과 manifest 파일 자신이다. `null` digest는 빈 집합이다. 이전 세대의 attestation(`resumed` 전의 것)은 $R$에 **포함**한다: 감사 기록이며 재서명하지 않기 때문이다. 작업 디렉터리(`writer-N`, `tasks/*`, `candidate-N`)는 blob으로 이미 보존되므로 $R$에 포함하지 않는다.
568
+
569
+ GC 대상은 $\operatorname{Files}(r) \setminus R(r)$이다. 단, 다음 조건에서는 GC를 거부한다.
570
+
571
+ - 살아 있는 owner lease가 있다(`lease_held`).
572
+ - `activeExecutionIds`가 비어 있지 않거나 `settlement !== "settled"`(`unsettled`).
573
+ - 원장 읽기가 실패한다(`integrity`). 손상 run은 GC하지 않는다.
574
+ - `apply_intent`가 있고 `applied`/`apply_diverged`가 없다(§6.4의 미결 적용).
575
+
576
+ ### 8.3 명령
577
+
578
+ `omk run gc ID --execute [--state-dir DIR]`. `--execute` 없이는 삭제 예정 목록과 바이트 수만 출력한다. 삭제는 파일 단위 `unlink`이며 디렉터리는 비었을 때만 제거한다. 삭제 전에 `gc_started` 이벤트, 삭제 후 `gc_finished {removedFiles, removedBytes}` 이벤트를 append한다. 두 이벤트 사이에서 죽으면 다음 GC가 같은 계산을 다시 하며, 이미 지워진 파일은 목록에 없으므로 멱등하다. run 전체 삭제(`--purge`)는 별도 명령이며 이 문서 범위 밖이다.
579
+
580
+ ### 8.4 수용 시험
581
+
582
+ | ID | 시나리오 | 요구 결과 |
583
+ | --- | --- | --- |
584
+ | G01 | 완료된 run의 writer 디렉터리 | 삭제, blob·attestation·journal 보존, `evidence()` 여전히 성공 |
585
+ | G02 | 재시도로 세대 2인 run | 세대 1의 실패 attempt 디렉터리 삭제, adoption blob 보존 |
586
+ | G03 | 활성 실행 중 | `unsettled` 거부 |
587
+ | G04 | 원장 손상 | `integrity` 거부, 파일 무변경 |
588
+ | G05 | 미결 apply_intent | 거부 |
589
+ | G06 | GC 중 SIGKILL 후 재실행 | 잔여 파일만 삭제, 오류 없음 |
590
+ | G07 | `--execute` 없음 | 삭제 0, 목록만 |
591
+
592
+ ## 9. U8: MCP loadout — 필요한 서버만 연결
593
+
594
+ ### 9.1 현재 소스 근거
595
+
596
+ `McpManager.listToolDefinitions()`(`packages/coding-agent/src/core/mcp/manager.ts:94`)는 `Promise.all([...this.runtimes.values()].map(ensureConnected))`로 **모든 활성 서버를 함께 연결**한다. `AgentSession.attachMcpServers()`(`agent-session.ts:4333`)는 이 함수를 호출한다. 클래스 주석의 "lazy"는 `attachMcpServers()`가 호출되기 전에는 spawn하지 않는다는 뜻이며, 호출 시점에는 전체 연결이다.
597
+
598
+ ### 9.2 설계
599
+
600
+ 세 계층으로 나눈다.
601
+
602
+ 1. **inventory**: 설정된 서버 이름 목록. spawn 없음. 현재 `serverNames` getter로 이미 가능하다.
603
+ 2. **manifest**: 서버별 도구 schema의 로컬 캐시 `~/.omk/agent/mcp-manifests/<name>.json`(`{configDigest, tools: [...], capturedAt}`). `configDigest`는 `McpServerConfig`에서 `env` 값을 제외한 `{command, args, cwd}`의 digest다. manifest가 있고 configDigest가 같으면 spawn 없이 도구 정의를 등록할 수 있다.
604
+ 3. **connect**: 실제 spawn. 도구가 **처음 호출될 때** 또는 manifest가 없을 때만 수행한다.
605
+
606
+ `attachMcpServers(options)`에 `loadout?: readonly string[]`을 추가한다.
607
+
608
+ - `loadout`이 주어지면 그 이름의 서버만 대상으로 한다. 목록에 없는 서버는 `status()`에 `idle`로 남고 spawn하지 않는다.
609
+ - `loadout`이 없으면 기존 동작(전체 연결)을 유지한다. 기본값 변경은 이 단계에서 하지 않는다.
610
+ - 대상 서버 중 manifest가 있는 것은 manifest로 도구를 등록하고 `state: "cached"`(새 상태)로 둔다. 첫 도구 호출 시 `ensureConnected()`가 spawn하고, 연결 후 `listTools()` 결과가 manifest와 다르면 호출을 `mcp_schema_drift`로 거부하고 manifest를 갱신하지 않는다(사용자가 `omk mcp refresh <name>`으로 갱신).
611
+ - manifest가 없는 대상 서버는 기존처럼 즉시 연결한다.
612
+
613
+ 동시 첫 호출은 기존 `runtime.connecting` promise 공유로 중복 spawn을 막는다(`ensureConnected()`의 현재 구현). 이 부분은 변경하지 않는다.
614
+
615
+ ### 9.3 계약 결합
616
+
617
+ verified-run의 live profile(§5)은 MCP 도구를 아직 허용하지 않으므로(`tools: ["verified_shell"]`), 이 절은 일반 세션에만 적용된다. 이후 live profile에 MCP를 허용할 때는 계약에 `mcpLoadout: readonly string[]`과 각 서버의 `manifestDigest`를 고정하고, drift 시 run을 `mcp_schema_drift`로 정지한다.
618
+
619
+ ### 9.4 수용 시험
620
+
621
+ | ID | 시나리오 | 요구 결과 |
622
+ | --- | --- | --- |
623
+ | M01 | 서버 3개 설정, loadout 1개 | spawn 1, 나머지 `idle` |
624
+ | M02 | loadout 서버에 manifest 존재 | spawn 0, 도구 등록, 첫 호출 시 spawn 1 |
625
+ | M03 | 첫 호출 후 schema drift | 호출 거부, manifest 무변경 |
626
+ | M04 | loadout 미지정 | 기존 전체 연결 동작 유지 |
627
+ | M05 | 동시 첫 호출 2회 | spawn 1 |
628
+ | M06 | loadout 서버 실패 | 다른 서버 영향 없음, 상태 `failed` |
629
+ | M07 | manifest configDigest 불일치 | manifest 무시, 즉시 연결 |
630
+
631
+ 측정: `packages/coding-agent/test/mcp/fake-server.mjs`를 사용해 spawn 횟수를 세고, `cold start` 시간과 서버 수의 관계를 `omk doctor resources --report`와 별도로 기록한다. PSS 측정은 Linux `/proc/<pid>/smaps_rollup`을 읽는 테스트 헬퍼로 한다.
632
+
633
+ ## 10. U9: 도구 호출 frontier
634
+
635
+ ### 10.1 현재 소스 근거
636
+
637
+ - `assignDagDependencies()`(`packages/agent/src/tool-dag-scheduler.ts:192`)는 각 호출의 직접 선행 충돌 집합을 계산하지만, live executor는 사용하지 않는다.
638
+ - `executeToolCallsDagLevels()`(`agent-loop.ts:958`)는 `schedulePlannedDagLevels()`로 레벨을 만들고 `runDagLevelCalls()`를 레벨마다 `await`한다. `runDagLevelCalls()` 안에서 승인(`authorizePlannedToolCall`) 후 `rescheduleRunnableLevels()`로 최종 인수 기준 재계획을 한 뒤 `Promise.all`로 레벨을 실행한다.
639
+ - 결과는 `finalizedByIndex`에 모아 source order로 emit한다.
640
+
641
+ ### 10.2 설계 원칙
642
+
643
+ 설계서 §11.4의 여섯 규칙을 그대로 따른다. 핵심은 **승인·재계획 경계를 레벨 단위에서 호출 단위로 옮기지 않는 것**이다. hook이 인수를 바꿀 수 있으므로, 최종 인수는 승인 직후에만 확정되고 그 시점의 claim으로만 충돌을 판단해야 한다.
644
+
645
+ ### 10.3 알고리즘
646
+
647
+ 1. batch 전체를 `planToolCall()`로 계획한다(현재와 동일).
648
+ 2. **승인 단계는 source order로 순차 수행**한다(현재는 레벨 단위로 순차). 각 호출은 승인 직후 최종 인수로 claim을 resolve하고, `entries[i]`에 저장한다. 승인 대기 중 사용자 응답이 필요한 hook은 여기서 자연히 직렬화된다.
649
+ 3. 승인이 끝난 호출 $i$에 대해, 이미 승인된 $j < i$ 중 `resolutionsConflict(entries[j], entries[i])`인 집합을 $\operatorname{pred}(i)$로 계산한다(`assignDagDependencies()`의 per-entry 버전).
650
+ 4. 실행은 별도 루프에서 frontier 방식으로 한다.
651
+
652
+ $$
653
+ \operatorname{Ready}(i, t) \iff
654
+ \operatorname{Approved}(i)
655
+ \ \land\
656
+ \forall j \in \operatorname{pred}(i):\ \operatorname{Done}(j, t)
657
+ \ \land\
658
+ |\operatorname{Running}(t)| < \text{maxConcurrency}
659
+ $$
660
+
661
+ `Done(j)`는 `finalizeExecutedToolCall()`까지 끝난 상태다. `hasUnsettledTimeout()`이 참인 호출은 `Done`이 아니라 `Unsettled`이며, 그 호출과 충돌하는 후속 호출은 영원히 Ready가 되지 않고 batch를 `stoppedByUnsettledTimeout`로 종료한다(현재 의미 보존).
662
+
663
+ 5. 결과 emit은 현재와 같이 batch 종료 후 source order로 한다. lifecycle start 이벤트는 실제 실행 시작 시점에 emit한다.
664
+
665
+ ### 10.4 보존해야 하는 의미
666
+
667
+ | 현재 의미 | frontier에서의 처리 |
668
+ | --- | --- |
669
+ | 승인 hook이 인수를 바꾸면 최종 인수로 재계획 | 승인 직후 claim resolve이므로 자동 반영 |
670
+ | 해석 불가 인수는 exclusive barrier | `resolution.kind === "exclusive"`는 모든 이전 호출과 충돌·모든 이후 호출이 이를 기다림 |
671
+ | `toolPolicies`의 `sequential` 도구 | 같은 도구 이름의 모든 호출을 서로 충돌로 취급 |
672
+ | unsettled timeout 후 나머지 skip | 위 §10.3 4항 |
673
+ | `signal.aborted` 시 나머지 `Operation aborted` | Ready 판정 전 signal 확인, 미시작 호출은 aborted 결과 |
674
+ | 결과 source order | 변경 없음 |
675
+
676
+ ### 10.5 승격 조건
677
+
678
+ 이 변경은 `AgentLoopConfig.toolScheduling: "dag-v2" | "dag-frontier-v1"` 옵션으로 추가하고 기본값은 `dag-v2`를 유지한다. 승격은 설계서 §19.2의 `observe → opt-in → bounded default` 순서를 따르며, opt-in 단계에서 다음 회귀가 모두 통과해야 한다.
679
+
680
+ | ID | 시나리오 | 요구 결과 |
681
+ | --- | --- | --- |
682
+ | F01 | A(느림)·B(독립)·C(A 의존) | C가 B 완료 전에 시작 |
683
+ | F02 | hook이 B의 인수를 A와 충돌하도록 변경 | B가 A 완료를 기다림 |
684
+ | F03 | symlink alias 두 경로 | canonical claim으로 충돌 인식 |
685
+ | F04 | 실행 중 cap 변경 없음(cap은 batch 시작 시 고정) | cap 초과 시작 0 |
686
+ | F05 | A timeout 후 늦은 쓰기 | A와 충돌하는 C 미시작, `stoppedByUnsettledTimeout` |
687
+ | F06 | 승인 대기 중 취소 | 미승인 호출 aborted, 실행 중 호출 회수 |
688
+ | F07 | 실패 결과 source order | 기존 테스트 통과 |
689
+ | F08 | `sequential` 도구 3회 호출 | 직렬 실행 |
690
+ | F09 | 기존 `dag-v2` 회귀 전체 | 옵션 미지정 시 통과 |
691
+
692
+ 성능 주장은 F01 같은 합성 시간 예시로만 하며, 실제 이득은 §18 실험 후에만 보고한다.
693
+
694
+ ## 11. U10: TUI/RPC 제어 표면
695
+
696
+ ### 11.1 원칙
697
+
698
+ CLI(`verified-run-cli.ts`)와 SDK(`RunCoordinator`)가 이미 있으므로, TUI/RPC는 **새 실행 경로를 만들지 않고 같은 Coordinator를 호출**한다. RPC 명령은 JSON이므로 `parseRun*Command()`를 그대로 사용한다.
699
+
700
+ ### 11.2 RPC
701
+
702
+ 기존 RPC 모드(`packages/coding-agent/docs/rpc.md`)에 다음 명령을 추가한다. 응답은 `RunProjection`/`RecoveryInspection`/`VerifiedRunEvidence`를 그대로 직렬화한다.
703
+
704
+ | RPC 명령 | Coordinator 호출 | 승인 |
705
+ | --- | --- | --- |
706
+ | `run.plan {contract}` | `planVerifiedRun` | 불필요(읽기) |
707
+ | `run.inspect {runId, mode?}` | `inspect`/`inspectRecovery`/… | 불필요 |
708
+ | `run.evidence {runId}` | `evidence` | 불필요 |
709
+ | `run.artifact {runId, candidateDigest, path}` | `artifact` | 불필요 |
710
+ | `run.start {contract, command, approvedContractDigest}` | `start` | RPC 호출자가 host 승인 채널 |
711
+ | `run.resume/restartWriter/retryTasks/amend/apply` | 각 메서드 | 동일 |
712
+ | `run.cancel {runId, commandId}` | §7 | 불필요(요청 파일만) |
713
+
714
+ RPC 호출자는 신뢰하는 host다(`verified-run.md` "SDK 호출자는 신뢰하는 host이고 승인 채널 인증을 책임집니다"). `approvedContractDigest`를 RPC payload로 받는 것은 이 신뢰 가정 안에서만 유효하며, 문서에 명시한다. 불신 클라이언트에 RPC를 노출하는 배포는 지원 범위 밖이다.
715
+
716
+ ### 11.3 TUI
717
+
718
+ `/run` 슬래시 명령 하나로 시작한다: `/run inspect ID`, `/run evidence ID`, `/run cancel ID`. 상태 표시는 `RunProjection`의 `execution/settlement/verification/application` 네 축을 각각 보여주고 하나의 boolean으로 합치지 않는다(설계서 §8.1). `start/apply`는 승인 digest 입력이 필요하므로 TUI에서는 **계약 digest를 화면에 표시하고 사용자가 같은 값을 타이핑**해야 진행한다. 클릭 한 번 승인은 두지 않는다.
719
+
720
+ ### 11.4 수용 시험
721
+
722
+ | ID | 시나리오 | 요구 결과 |
723
+ | --- | --- | --- |
724
+ | R01 | RPC `run.start` → `run.inspect` | CLI와 같은 projection |
725
+ | R02 | RPC payload에 `approved: true` 필드 | parser 거부 |
726
+ | R03 | TUI `/run inspect` | 네 축 표시, JSON과 동일 값 |
727
+ | R04 | TUI apply 승인 digest 오타 | 거부, 실행 없음 |
728
+ | R05 | RPC 중 연결 끊김 | Coordinator는 계속 실행, 재연결 후 inspect로 상태 확인 |
729
+
730
+ ## 12. U11: 전체 crash window 복구
731
+
732
+ ### 12.1 현재 복구 가능 상태
733
+
734
+ `verified-run.md` "`resume`으로 복구하지 않는 경우" 목록과 `work-recovery.ts`의 `assertWorkRecoverable()`을 기준으로, 자동 복구가 차단되는 창은 다음이다.
735
+
736
+ | 창 | 상태 | 현재 처리 |
737
+ | --- | --- | --- |
738
+ | W1 | `created` 후 `budget_anchored` 전 | `legacy`/`input_checkpoint_missing`으로 차단 |
739
+ | W2 | `budget_anchored` 후 `input_checkpoint` 전 | 동일 |
740
+ | W3 | `dispatch` 후 `process_ready` 전 | namespace identity 없음 → 차단 |
741
+ | W4 | `process_ready` 후 `exited` 전(살아 있음) | live namespace → 차단(정상) |
742
+ | W5 | `exited` 후 `task_finished`/`candidate` 전 | 출력 미고정 → 재시도 명령으로 복구 가능 |
743
+ | W6 | `candidate` 후 `evaluated` 전 | `resume`으로 복구 가능 |
744
+ | W7 | `evaluated` 후 attestation 파일 fsync 전 | `evidence()` 실패 → `integrity` |
745
+
746
+ ### 12.2 설계
747
+
748
+ W1·W2는 **자동 복구 대상이 아니다**. writer가 시작되기 전이므로 부작용이 없고, 사용자는 같은 계약으로 새 `runId`를 시작하면 된다. 다만 `inspect --recovery`가 이를 `reason: "not_started"`로 명확히 분류하고 "새 run 시작"을 안내하도록 한다.
749
+
750
+ W3은 §12.3에서 다룬다. W4는 정상 동작이다. W5는 현재 `retry-tasks`/`restart-writer`가 처리한다. W6은 `resume`이 처리한다.
751
+
752
+ W7은 순서를 바꿔 닫는다: `verifyCandidate()`가 `issueRunEvidence()`로 attestation을 **먼저** fsync하고, 그 다음 `evaluated` 이벤트를 append한다. 현재 코드가 이미 이 순서다(`issueRunEvidence` → `journal.append({kind: "evaluated"})`). 따라서 W7의 실제 창은 "attestation은 있으나 `evaluated`가 없음"이며, 이 경우 `resume`이 새 세대에서 검사를 다시 실행한다(재서명 아님). 이 동작이 현재 구현과 일치하는지 확인하는 회귀 시험 `verified-run-crash.test.ts`에 사례를 추가한다.
753
+
754
+ ### 12.3 W3: dispatch 후 process_ready 전
755
+
756
+ 이 창은 bwrap이 시작됐는지 알 수 없다. 현재는 차단이 맞다. 자동화할 수 있는 부분은 **판정**뿐이다: `process-gate.ts`의 stdin gate는 `process_ready`가 fsync된 뒤에만 `go`를 보내므로, `process_ready` 기록이 없으면 gate가 열리지 않았고 **명령은 실행되지 않았다**. 따라서 W3의 자식은 있어도 유휴 상태이며, 종료해도 부작용이 없다.
757
+
758
+ 이를 근거로 `inspect --recovery`는 W3을 `reason: "dispatch_without_ready"`로 분류하고, `retry-tasks`/`restart-writer`는 W3 상태의 dispatch를 **`exited {failure: "interrupted_before_ready"}`로 정산**한 뒤 진행할 수 있다. 조건은 다음이다.
759
+
760
+ $$
761
+ \operatorname{SafeToReconcile}(x) \iff
762
+ \nexists\, \text{process\_ready}(x)
763
+ \ \land\
764
+ \big(\operatorname{NamespaceAlive}(x) = \text{false} \lor \operatorname{NamespaceUnknown}(x)\big)
765
+ $$
766
+
767
+ namespace가 살아 있으면(gate 대기 중인 유휴 bwrap) 먼저 SIGKILL하고 close를 확인한 뒤 정산한다. identity가 없어 alive 여부를 알 수 없는 경우는 gate가 열리지 않았음이 원장으로 증명되므로 정산할 수 있다. 이 정산은 새 세대 진입 시 `reconciledExecutionIds`에 기록한다(기존 필드 재사용).
768
+
769
+ ### 12.4 수용 시험
770
+
771
+ | ID | 시나리오 | 요구 결과 |
772
+ | --- | --- | --- |
773
+ | K01 | `created` 직후 SIGKILL | `not_started`, 새 run 안내 |
774
+ | K02 | `dispatch` 직후(ready 전) SIGKILL, 자식 없음 | `dispatch_without_ready`, retry 시 `interrupted_before_ready` 정산 후 진행 |
775
+ | K03 | 위와 같되 유휴 bwrap 생존 | SIGKILL → close 확인 → 정산 |
776
+ | K04 | `process_ready` 후 SIGKILL, 자식 생존 | 차단 유지(`namespace_alive`) |
777
+ | K05 | attestation 후 `evaluated` 전 SIGKILL | resume이 새 세대 재검사, 이전 attestation 보존 |
778
+ | K06 | `evaluated` 후 SIGKILL | inspect/evidence 정상 |
779
+
780
+ ## 13. 구현 순서의 근거
781
+
782
+ | 순서 | 항목 | 이유 |
783
+ | --- | --- | --- |
784
+ | 1 | U1 | 완료 (`decee7f157`). 다른 항목의 기준선 |
785
+ | 2 | U5 apply | 사용자가 결과를 실제로 쓰는 첫 경로. verified-run의 제품 가치가 여기서 생김 |
786
+ | 3 | U6 cancel | apply와 같은 명령 인프라(`withRecoveryLease`, 요청 파일) 재사용 |
787
+ | 4 | U4 live adapter | 실제 모델 없이는 U2·U3의 효용을 평가할 수 없음. 기존 model-contract 재사용 |
788
+ | 5 | U2 verification edge | live adapter 위에서 작업 단위 검사가 의미를 가짐 |
789
+ | 6 | U8 MCP loadout | 일반 세션 개선. verified-run과 독립이지만 U4 이후 live profile에 결합 |
790
+ | 7 | U11 crash window | apply/cancel/live가 추가된 뒤 창 목록을 재확정 |
791
+ | 8 | U3 amendment | U2의 stable node digest가 필요 |
792
+ | 9 | U7 GC | apply_intent 등 모든 파일 종류가 정해진 뒤 |
793
+ | 10 | U10 TUI/RPC | 명령 집합이 안정된 뒤 |
794
+ | 11 | U9 tool frontier | 별도 패키지(`omk-agent-core`), 독립 승격 경로 |
795
+
796
+ 각 단위는 `programming` 스킬의 RED→GREEN 규칙을 따른다: 실패하는 테스트를 먼저 작성하고 실제 실패를 기록한 뒤 구현한다. 모듈은 250 pure-LOC 한도를 유지하며, `coordinator.ts`(219줄)와 `projection.ts`는 U3 이전에 책임별 분할이 필요하다.
797
+
798
+ ## 14. 문서 검증 방법
799
+
800
+ 이 문서의 수식은 다음으로 검사한다.
801
+
802
+ ```bash
803
+ python3 - <<'PY'
804
+ import re, subprocess, tempfile, pathlib
805
+ text = pathlib.Path("packages/coding-agent/docs/verified-run-remaining-design.md").read_text()
806
+ blocks = re.findall(r"\$\$(.+?)\$\$", text, re.S)
807
+ body = "\n".join(f"\\begin{{equation*}}{b}\\end{{equation*}}" for b in blocks)
808
+ src = "\\documentclass{article}\\usepackage{amsmath,amssymb}\\begin{document}" + body + "\\end{document}"
809
+ d = tempfile.mkdtemp()
810
+ pathlib.Path(d, "f.tex").write_text(src)
811
+ r = subprocess.run(["latex", "-interaction=nonstopmode", "-halt-on-error", "-output-directory", d, "f.tex"], capture_output=True, text=True, cwd=d)
812
+ print("blocks:", len(blocks), "exit:", r.returncode)
813
+ PY
814
+ ```
815
+
816
+ 종료 코드 0이 수식 문법의 통과 기준이다. 이 검사는 수식이 읽힐 수 있는지만 확인하며, 설계 가정의 정확성을 증명하지 않는다. 문서 내 소스 경로·기호명은 `rg`로 존재를 확인했고, 그 결과는 이 문서 끝의 검증 기록에 있다.
817
+
818
+ ## 15. 비목표와 남는 한계
819
+
820
+ - 임의 외부 API의 exactly-once, 모든 provider의 실제 과금 상한, 같은 UID 악성 프로세스로부터의 격리, Linux 외 OS는 여전히 범위 밖이다.
821
+ - U4의 `verified_shell`은 첫 도구이며 파일 편집 도구·MCP 도구는 후속이다.
822
+ - U5는 Git 통합을 하지 않는다. commit/branch는 사용자 책임이다.
823
+ - U9의 성능 이득은 §10.5 회귀 통과 후 설계서 §18의 동일 조건 실험으로만 주장한다.
824
+ - 이 문서는 구현 시간을 추정하지 않는다.
825
+
826
+ ## 부록 A. 새 오류 코드
827
+
828
+ 기존 `VerifiedRunError` 코드 체계에 추가하는 값이다. 모두 lowercase snake_case이며 기존 코드와 겹치지 않는다.
829
+
830
+ | 코드 | 절 | 의미 |
831
+ | --- | --- | --- |
832
+ | `edge claim` (RunContractError) | §3 | edge가 참조한 claimId가 선행 작업 checks에 없음 |
833
+ | `adoption_mismatch` | §4 | stable node digest 또는 blob 불일치 |
834
+ | `candidate_frozen` | §4 | candidate 고정 후 amend 시도 |
835
+ | `generation_limit` | §4 | 세대 상한 초과(기존 코드 재사용 가능 시 재사용) |
836
+ | `adapter_unsupported` | §5 | capability 행렬에 `unknown` 존재 |
837
+ | `model_request_limit` | §5 | 기존 코드 재사용 |
838
+ | `apply_not_requested` | §6 | `artifact-only` 계약에 apply |
839
+ | `base_moved` | §6 | 적용 직전 base digest 불일치 |
840
+ | `apply_partial` | §6 | 부분 적용 상태에서 재요청 |
841
+ | `no_owner` | §7 | 소유자 없는 run에 cancel |
842
+ | `lease_held` | §6, §8 | 다른 owner의 lease 보유 |
843
+ | `unsettled` | §8 | 활성 실행 중 GC |
844
+ | `mcp_schema_drift` | §9 | manifest와 실제 schema 불일치 |
845
+ | `not_started` | §12 | writer 시작 전 crash |
846
+ | `dispatch_without_ready` | §12 | gate 열리기 전 crash |
847
+
848
+ ## 부록 B. 참조한 소스 (2026-09-13 작업 트리)
849
+
850
+ | 파일 | 참조한 기호 |
851
+ | --- | --- |
852
+ | `packages/protocol/src/run-contract.ts` | `RunContract`, `RunCheck`, `RunPhaseBudget`, `parseRunContract`, `parseRunStartCommand` |
853
+ | `packages/protocol/src/run-dag.ts` | `RunDagTask`, `RunDagWriter`, `orderRunDag`, `runDagAncestors`, `parseRunDagWriter` |
854
+ | `packages/protocol/src/run-parsing.ts` | `runObject`, `runId`, `runDigest`, `runArray`, `runRelativePath`, `runAbsolutePath`, `runArgv` |
855
+ | `packages/protocol/src/run-task-retry.ts` | `RunTaskRetryCommand`, `parseRunTaskRetryCommand` |
856
+ | `packages/protocol/src/claims/claim-types.ts` | `ObservationSource`, `ClaimNode`, `ObservationNode`, `ProofClosureInput`, `WorkspaceCompleteness` |
857
+ | `packages/coding-agent/src/core/verified-run/coordinator.ts` | `RunCoordinator`, `VerifiedRunApproval`, `planVerifiedRun` |
858
+ | `.../verified-run/dag-phase.ts` | `executeDag`, `executeTask`, `observeWork` |
859
+ | `.../verified-run/dag-projection.ts` | `readyDagTasks`, `reduceDagEvent` |
860
+ | `.../verified-run/dag-types.ts` | `RunTaskExecution`, `RunTaskProjection`, `RunTaskCheckpoint`, `DagEvent` |
861
+ | `.../verified-run/dag-recovery.ts` | `inspectTaskRecovery`, `retryDagTasks`, `assertDagRecoverable` |
862
+ | `.../verified-run/run-types.ts` | `RunEvent`, `RunProjection`, `WriterReduction` |
863
+ | `.../verified-run/recovery-command.ts` | `commandDisposition`, `withRecoveryLease`, `requireRunJournal` |
864
+ | `.../verified-run/recovery-clock.ts` | `RunClock`, `RecoveryBudget`, `readRunClock`, `anchorRunBudget`, `remainingRunTime` |
865
+ | `.../verified-run/verification-phase.ts` | `verifyCandidate` |
866
+ | `.../verified-run/evidence.ts` | `issueRunEvidence`, `readRunEvidence`, `createRunIssuer` |
867
+ | `.../verified-run/evidence-binding.ts` | `CheckObservation`, `parseCheckObservations`, `closesRunClaims` |
868
+ | `.../verified-run/candidate.ts` | `CandidateManifest`, `CandidateSnapshot`, `captureCandidate`, `materializeCandidate`, `assertCandidateScope`, `storeCandidate`, `loadCandidate` |
869
+ | `.../verified-run/journal.ts` | `readRunJournal`, `VerifiedRunJournal`, `JournalSnapshot`, `journalPath` |
870
+ | `.../verified-run/owned-execution.ts` | `executeRunCommand`, `OwnedRunCommand` |
871
+ | `.../verified-run/scripted-writer.ts` | `executeScriptedWriter`, `ScriptedWriterContext` |
872
+ | `.../verified-run/session-port.ts` | `VerifiedRunSession`, `VerifiedRunRuntime` |
873
+ | `.../verified-run/process-gate.ts` | `PROCESS_GATE_ARGV`, `identityFromSandboxInfo` |
874
+ | `.../verified-run/storage.ts` | `VerifiedRunError`, `publishObject`, `publishBytes`, `readRegularFile`, `stateRunPath` |
875
+ | `packages/coding-agent/src/commands/verified-run-cli.ts` | `parse`, `runVerifiedRunCli` |
876
+ | `packages/coding-agent/src/core/mcp/manager.ts` | `McpManager`, `listToolDefinitions`, `ensureConnected`, `connect`, `checkHealth` |
877
+ | `packages/coding-agent/src/core/agent-session.ts` | `attachMcpServers` |
878
+ | `packages/agent/src/tool-dag-scheduler.ts` | `assignDagDependencies`, `applyConcurrencyCap`, `scheduleDagLevels` |
879
+ | `packages/agent/src/agent-loop.ts` | `executeToolCallsDagLevels`, `runDagLevelCalls` |
880
+ | `packages/coding-agent/docs/verified-run.md` | 구현·검증 상태표 |
881
+ | `packages/coding-agent/docs/model-contract.md` | Covered paths, Limits |