smol-symphony 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (540) hide show
  1. package/AGENTS.md +41 -22
  2. package/DESIGN.md +494 -273
  3. package/README.md +109 -57
  4. package/SPEC.md +33 -24
  5. package/WORKFLOW.minimal.yaml +34 -0
  6. package/{WORKFLOW.template.md → WORKFLOW.template.yaml} +409 -256
  7. package/WORKFLOW.yaml +487 -0
  8. package/assets/skills/symphony-issues/SKILL.md +136 -0
  9. package/assets/symphony-mise.system.toml +68 -0
  10. package/dist/bin/symphony.js +22 -786
  11. package/dist/bin/symphony.js.map +1 -1
  12. package/dist/core/actions/context.js +109 -0
  13. package/dist/core/actions/context.js.map +1 -0
  14. package/dist/{actions/parsing.js → core/actions/parse.js} +33 -114
  15. package/dist/core/actions/parse.js.map +1 -0
  16. package/dist/core/actions/plan.js +197 -0
  17. package/dist/core/actions/plan.js.map +1 -0
  18. package/dist/core/actions/predicates.js +111 -0
  19. package/dist/core/actions/predicates.js.map +1 -0
  20. package/dist/core/actions/run-fold.js +248 -0
  21. package/dist/core/actions/run-fold.js.map +1 -0
  22. package/dist/core/actions/template.js +118 -0
  23. package/dist/core/actions/template.js.map +1 -0
  24. package/dist/core/cli/args.js +116 -0
  25. package/dist/core/cli/args.js.map +1 -0
  26. package/dist/core/coerce.js +75 -0
  27. package/dist/core/coerce.js.map +1 -0
  28. package/dist/core/credential/account-id.js +20 -0
  29. package/dist/core/credential/account-id.js.map +1 -0
  30. package/dist/core/credential/adapter-config.js +136 -0
  31. package/dist/core/credential/adapter-config.js.map +1 -0
  32. package/dist/core/credential/availability.js +98 -0
  33. package/dist/core/credential/availability.js.map +1 -0
  34. package/dist/core/credential/extract.js +228 -0
  35. package/dist/core/credential/extract.js.map +1 -0
  36. package/dist/core/credential/fake-creds.js +171 -0
  37. package/dist/core/credential/fake-creds.js.map +1 -0
  38. package/dist/core/credential/identity.js +125 -0
  39. package/dist/core/credential/identity.js.map +1 -0
  40. package/dist/core/credential/shape.js +230 -0
  41. package/dist/core/credential/shape.js.map +1 -0
  42. package/dist/core/credential/strings.js +15 -0
  43. package/dist/core/credential/strings.js.map +1 -0
  44. package/dist/core/doctor/checks.js +303 -0
  45. package/dist/core/doctor/checks.js.map +1 -0
  46. package/dist/core/git/result.js +107 -0
  47. package/dist/core/git/result.js.map +1 -0
  48. package/dist/core/http/decisions.js +225 -0
  49. package/dist/core/http/decisions.js.map +1 -0
  50. package/dist/{http.js → core/http/render.js} +472 -738
  51. package/dist/core/http/render.js.map +1 -0
  52. package/dist/{http-handlers.js → core/http/routes.js} +52 -87
  53. package/dist/core/http/routes.js.map +1 -0
  54. package/dist/core/http/views.js +181 -0
  55. package/dist/core/http/views.js.map +1 -0
  56. package/dist/core/image/managed-image.js +95 -0
  57. package/dist/core/image/managed-image.js.map +1 -0
  58. package/dist/core/issue/file.js +149 -0
  59. package/dist/core/issue/file.js.map +1 -0
  60. package/dist/core/issue/parse.js +210 -0
  61. package/dist/core/issue/parse.js.map +1 -0
  62. package/dist/core/mcp/dispatch.js +239 -0
  63. package/dist/core/mcp/dispatch.js.map +1 -0
  64. package/dist/core/mcp/post-move.js +92 -0
  65. package/dist/core/mcp/post-move.js.map +1 -0
  66. package/dist/core/mcp/protocol.js +293 -0
  67. package/dist/core/mcp/protocol.js.map +1 -0
  68. package/dist/core/mcp/url.js +162 -0
  69. package/dist/core/mcp/url.js.map +1 -0
  70. package/dist/core/path.js +63 -0
  71. package/dist/core/path.js.map +1 -0
  72. package/dist/core/reconcile/image-decide.js +48 -0
  73. package/dist/core/reconcile/image-decide.js.map +1 -0
  74. package/dist/core/reconcile/ledger.js +142 -0
  75. package/dist/core/reconcile/ledger.js.map +1 -0
  76. package/dist/core/reconcile/pr-classify.js +62 -0
  77. package/dist/core/reconcile/pr-classify.js.map +1 -0
  78. package/dist/{reconciler → core/reconcile}/pr-decide.js +25 -12
  79. package/dist/core/reconcile/pr-decide.js.map +1 -0
  80. package/dist/core/reconcile/pr-loop.js +161 -0
  81. package/dist/core/reconcile/pr-loop.js.map +1 -0
  82. package/dist/core/reconcile/pr-notes.js +35 -0
  83. package/dist/core/reconcile/pr-notes.js.map +1 -0
  84. package/dist/core/reconcile/vm-decide.js +70 -0
  85. package/dist/core/reconcile/vm-decide.js.map +1 -0
  86. package/dist/core/reconcile/vm-reap.js +207 -0
  87. package/dist/core/reconcile/vm-reap.js.map +1 -0
  88. package/dist/core/reconcile/workspace-decide.js +162 -0
  89. package/dist/core/reconcile/workspace-decide.js.map +1 -0
  90. package/dist/core/runlog/summary.js +231 -0
  91. package/dist/core/runlog/summary.js.map +1 -0
  92. package/dist/core/runner/dispatch-config.js +95 -0
  93. package/dist/core/runner/dispatch-config.js.map +1 -0
  94. package/dist/core/runner/injection.js +61 -0
  95. package/dist/core/runner/injection.js.map +1 -0
  96. package/dist/core/runner/mise.js +210 -0
  97. package/dist/core/runner/mise.js.map +1 -0
  98. package/dist/core/runner/prompt.js +720 -0
  99. package/dist/core/runner/prompt.js.map +1 -0
  100. package/dist/core/runner/turn.js +242 -0
  101. package/dist/core/runner/turn.js.map +1 -0
  102. package/dist/core/runner/vm-plan.js +390 -0
  103. package/dist/core/runner/vm-plan.js.map +1 -0
  104. package/dist/core/schedule/admission.js +123 -0
  105. package/dist/core/schedule/admission.js.map +1 -0
  106. package/dist/core/schedule/circuit-breaker.js +111 -0
  107. package/dist/core/schedule/circuit-breaker.js.map +1 -0
  108. package/dist/core/schedule/eligibility.js +83 -0
  109. package/dist/core/schedule/eligibility.js.map +1 -0
  110. package/dist/core/schedule/reconcile-issue.js +82 -0
  111. package/dist/core/schedule/reconcile-issue.js.map +1 -0
  112. package/dist/core/schedule/retry.js +96 -0
  113. package/dist/core/schedule/retry.js.map +1 -0
  114. package/dist/core/schedule/sleep-cycle.js +133 -0
  115. package/dist/core/schedule/sleep-cycle.js.map +1 -0
  116. package/dist/core/schedule/slots.js +124 -0
  117. package/dist/core/schedule/slots.js.map +1 -0
  118. package/dist/core/schedule/tick.js +553 -0
  119. package/dist/core/schedule/tick.js.map +1 -0
  120. package/dist/core/schedule/token-fold.js +181 -0
  121. package/dist/core/schedule/token-fold.js.map +1 -0
  122. package/dist/core/state-resolve.js +86 -0
  123. package/dist/core/state-resolve.js.map +1 -0
  124. package/dist/core/vm-guards.js +278 -0
  125. package/dist/core/vm-guards.js.map +1 -0
  126. package/dist/core/workflow/derive.js +107 -0
  127. package/dist/core/workflow/derive.js.map +1 -0
  128. package/dist/core/workflow/parse.js +687 -0
  129. package/dist/core/workflow/parse.js.map +1 -0
  130. package/dist/core/workflow/prompt-probe.js +78 -0
  131. package/dist/core/workflow/prompt-probe.js.map +1 -0
  132. package/dist/core/workflow/validate.js +189 -0
  133. package/dist/core/workflow/validate.js.map +1 -0
  134. package/dist/core/workspace-key.js +19 -0
  135. package/dist/core/workspace-key.js.map +1 -0
  136. package/dist/shell/actions-runner.js +356 -0
  137. package/dist/shell/actions-runner.js.map +1 -0
  138. package/dist/shell/adapter/adapter-registry.js +45 -0
  139. package/dist/shell/adapter/adapter-registry.js.map +1 -0
  140. package/dist/shell/adapter/clock-random.js +96 -0
  141. package/dist/shell/adapter/clock-random.js.map +1 -0
  142. package/dist/shell/adapter/gondolin-dispatch-helpers.js +158 -0
  143. package/dist/shell/adapter/gondolin-dispatch-helpers.js.map +1 -0
  144. package/dist/shell/adapter/gondolin-dispatch.js +385 -0
  145. package/dist/shell/adapter/gondolin-dispatch.js.map +1 -0
  146. package/dist/shell/adapter/gondolin-image-converter.js +233 -0
  147. package/dist/shell/adapter/gondolin-image-converter.js.map +1 -0
  148. package/dist/shell/adapter/gondolin-image-fetch.js +180 -0
  149. package/dist/shell/adapter/gondolin-image-fetch.js.map +1 -0
  150. package/dist/shell/adapter/launcher-asset.js +57 -0
  151. package/dist/shell/adapter/launcher-asset.js.map +1 -0
  152. package/dist/shell/adapter/mise-config-asset.js +65 -0
  153. package/dist/shell/adapter/mise-config-asset.js.map +1 -0
  154. package/dist/shell/adapter/workflow-loader.js +304 -0
  155. package/dist/shell/adapter/workflow-loader.js.map +1 -0
  156. package/dist/shell/cli/doctor.js +268 -0
  157. package/dist/shell/cli/doctor.js.map +1 -0
  158. package/dist/shell/effect-interpreter-families.js +314 -0
  159. package/dist/shell/effect-interpreter-families.js.map +1 -0
  160. package/dist/shell/effect-interpreter.js +29 -0
  161. package/dist/shell/effect-interpreter.js.map +1 -0
  162. package/dist/shell/interp/acp-frame.js +137 -0
  163. package/dist/shell/interp/acp-frame.js.map +1 -0
  164. package/dist/shell/interp/acp-ws-conn.js +320 -0
  165. package/dist/shell/interp/acp-ws-conn.js.map +1 -0
  166. package/dist/shell/interp/acp-ws-frames.js +159 -0
  167. package/dist/shell/interp/acp-ws-frames.js.map +1 -0
  168. package/dist/shell/interp/acp-ws.js +197 -0
  169. package/dist/shell/interp/acp-ws.js.map +1 -0
  170. package/dist/shell/interp/acp.js +319 -0
  171. package/dist/shell/interp/acp.js.map +1 -0
  172. package/dist/shell/interp/credential-defaults.js +128 -0
  173. package/dist/shell/interp/credential-defaults.js.map +1 -0
  174. package/dist/shell/interp/credential-hooks.js +149 -0
  175. package/dist/shell/interp/credential-hooks.js.map +1 -0
  176. package/dist/shell/interp/credential-registry.js +226 -0
  177. package/dist/shell/interp/credential-registry.js.map +1 -0
  178. package/dist/shell/interp/credential.js +103 -0
  179. package/dist/shell/interp/credential.js.map +1 -0
  180. package/dist/shell/interp/gh.js +163 -0
  181. package/dist/shell/interp/gh.js.map +1 -0
  182. package/dist/shell/interp/git.js +28 -0
  183. package/dist/shell/interp/git.js.map +1 -0
  184. package/dist/shell/interp/log.js +213 -0
  185. package/dist/shell/interp/log.js.map +1 -0
  186. package/dist/shell/interp/process.js +178 -0
  187. package/dist/shell/interp/process.js.map +1 -0
  188. package/dist/shell/interp/runlog.js +193 -0
  189. package/dist/shell/interp/runlog.js.map +1 -0
  190. package/dist/shell/interp/timer.js +64 -0
  191. package/dist/shell/interp/timer.js.map +1 -0
  192. package/dist/shell/interp/tracker-disk.js +99 -0
  193. package/dist/shell/interp/tracker-disk.js.map +1 -0
  194. package/dist/shell/interp/tracker-parse.js +71 -0
  195. package/dist/shell/interp/tracker-parse.js.map +1 -0
  196. package/dist/shell/interp/tracker-scan.js +238 -0
  197. package/dist/shell/interp/tracker-scan.js.map +1 -0
  198. package/dist/shell/interp/tracker-write.js +91 -0
  199. package/dist/shell/interp/tracker-write.js.map +1 -0
  200. package/dist/shell/interp/tracker.js +41 -0
  201. package/dist/shell/interp/tracker.js.map +1 -0
  202. package/dist/shell/interp/tty.js +48 -0
  203. package/dist/shell/interp/tty.js.map +1 -0
  204. package/dist/shell/interp/vm.js +199 -0
  205. package/dist/shell/interp/vm.js.map +1 -0
  206. package/dist/shell/interp/workspace.js +310 -0
  207. package/dist/shell/interp/workspace.js.map +1 -0
  208. package/dist/shell/main-acp.js +78 -0
  209. package/dist/shell/main-acp.js.map +1 -0
  210. package/dist/shell/main-adapters.js +222 -0
  211. package/dist/shell/main-adapters.js.map +1 -0
  212. package/dist/shell/main-credential.js +122 -0
  213. package/dist/shell/main-credential.js.map +1 -0
  214. package/dist/shell/main-doctor.js +22 -0
  215. package/dist/shell/main-doctor.js.map +1 -0
  216. package/dist/shell/main-entry.js +46 -0
  217. package/dist/shell/main-entry.js.map +1 -0
  218. package/dist/shell/main-http-csrf.js +45 -0
  219. package/dist/shell/main-http-csrf.js.map +1 -0
  220. package/dist/shell/main-http-handler.js +389 -0
  221. package/dist/shell/main-http-handler.js.map +1 -0
  222. package/dist/shell/main-http-mcp.js +122 -0
  223. package/dist/shell/main-http-mcp.js.map +1 -0
  224. package/dist/shell/main-http-views.js +253 -0
  225. package/dist/shell/main-http-views.js.map +1 -0
  226. package/dist/shell/main-http.js +76 -0
  227. package/dist/shell/main-http.js.map +1 -0
  228. package/dist/shell/main-loops.js +130 -0
  229. package/dist/shell/main-loops.js.map +1 -0
  230. package/dist/shell/main-mcp.js +129 -0
  231. package/dist/shell/main-mcp.js.map +1 -0
  232. package/dist/shell/main-orchestrator.js +120 -0
  233. package/dist/shell/main-orchestrator.js.map +1 -0
  234. package/dist/shell/main-preflight.js +43 -0
  235. package/dist/shell/main-preflight.js.map +1 -0
  236. package/dist/shell/main-reconcilers-helpers.js +244 -0
  237. package/dist/shell/main-reconcilers-helpers.js.map +1 -0
  238. package/dist/shell/main-reconcilers-pr.js +148 -0
  239. package/dist/shell/main-reconcilers-pr.js.map +1 -0
  240. package/dist/shell/main-reconcilers.js +225 -0
  241. package/dist/shell/main-reconcilers.js.map +1 -0
  242. package/dist/shell/main-runner.js +355 -0
  243. package/dist/shell/main-runner.js.map +1 -0
  244. package/dist/shell/main-scaffold.js +116 -0
  245. package/dist/shell/main-scaffold.js.map +1 -0
  246. package/dist/shell/main-shutdown.js +115 -0
  247. package/dist/shell/main-shutdown.js.map +1 -0
  248. package/dist/shell/main-startup.js +48 -0
  249. package/dist/shell/main-startup.js.map +1 -0
  250. package/dist/shell/main-substrates.js +43 -0
  251. package/dist/shell/main-substrates.js.map +1 -0
  252. package/dist/shell/main.js +385 -0
  253. package/dist/shell/main.js.map +1 -0
  254. package/dist/shell/orchestrator-feedback.js +69 -0
  255. package/dist/shell/orchestrator-feedback.js.map +1 -0
  256. package/dist/shell/orchestrator-image.js +167 -0
  257. package/dist/shell/orchestrator-image.js.map +1 -0
  258. package/dist/shell/orchestrator-loop.js +468 -0
  259. package/dist/shell/orchestrator-loop.js.map +1 -0
  260. package/dist/shell/orchestrator-reconcile.js +36 -0
  261. package/dist/shell/orchestrator-reconcile.js.map +1 -0
  262. package/dist/shell/reconciler-loop.js +228 -0
  263. package/dist/shell/reconciler-loop.js.map +1 -0
  264. package/dist/shell/runner-loop-turn.js +301 -0
  265. package/dist/shell/runner-loop-turn.js.map +1 -0
  266. package/dist/shell/runner-loop.js +338 -0
  267. package/dist/shell/runner-loop.js.map +1 -0
  268. package/dist/shell/server/http.js +208 -0
  269. package/dist/shell/server/http.js.map +1 -0
  270. package/dist/shell/server/mcp-runtime-effects.js +237 -0
  271. package/dist/shell/server/mcp-runtime-effects.js.map +1 -0
  272. package/dist/shell/server/mcp-runtime.js +99 -0
  273. package/dist/shell/server/mcp-runtime.js.map +1 -0
  274. package/dist/shell/workspace-key.js +14 -0
  275. package/dist/shell/workspace-key.js.map +1 -0
  276. package/dist/types/acp.js +8 -0
  277. package/dist/types/acp.js.map +1 -0
  278. package/dist/types/actions/plan.js +6 -0
  279. package/dist/types/actions/plan.js.map +1 -0
  280. package/dist/types/actions/predicates.js +6 -0
  281. package/dist/types/actions/predicates.js.map +1 -0
  282. package/dist/types/actions/run-fold.js +8 -0
  283. package/dist/types/actions/run-fold.js.map +1 -0
  284. package/dist/types/actions.js +7 -0
  285. package/dist/types/actions.js.map +1 -0
  286. package/dist/types/adapter/clock-random.js +4 -0
  287. package/dist/types/adapter/clock-random.js.map +1 -0
  288. package/dist/types/adapter/gondolin-image-converter.js +5 -0
  289. package/dist/types/adapter/gondolin-image-converter.js.map +1 -0
  290. package/dist/types/adapter/gondolin-image-fetch.js +5 -0
  291. package/dist/types/adapter/gondolin-image-fetch.js.map +1 -0
  292. package/dist/types/adapter/workflow-loader.js +4 -0
  293. package/dist/types/adapter/workflow-loader.js.map +1 -0
  294. package/dist/types/cli/args.js +8 -0
  295. package/dist/types/cli/args.js.map +1 -0
  296. package/dist/types/config.js +8 -0
  297. package/dist/types/config.js.map +1 -0
  298. package/dist/types/credential-interp.js +6 -0
  299. package/dist/types/credential-interp.js.map +1 -0
  300. package/dist/types/credentials.js +10 -0
  301. package/dist/types/credentials.js.map +1 -0
  302. package/dist/types/doctor.js +7 -0
  303. package/dist/types/doctor.js.map +1 -0
  304. package/dist/types/domain.js +7 -0
  305. package/dist/types/domain.js.map +1 -0
  306. package/dist/types/effect.js +15 -0
  307. package/dist/types/effect.js.map +1 -0
  308. package/dist/types/errors.js +39 -0
  309. package/dist/types/errors.js.map +1 -0
  310. package/dist/types/http/decisions.js +6 -0
  311. package/dist/types/http/decisions.js.map +1 -0
  312. package/dist/types/http/render.js +10 -0
  313. package/dist/types/http/render.js.map +1 -0
  314. package/dist/types/http/views.js +6 -0
  315. package/dist/types/http/views.js.map +1 -0
  316. package/dist/types/http.js +9 -0
  317. package/dist/types/http.js.map +1 -0
  318. package/dist/types/image/managed-image.js +7 -0
  319. package/dist/types/image/managed-image.js.map +1 -0
  320. package/dist/types/interp/effect-interpreter.js +8 -0
  321. package/dist/types/interp/effect-interpreter.js.map +1 -0
  322. package/dist/types/interp/tracker.js +7 -0
  323. package/dist/types/interp/tracker.js.map +1 -0
  324. package/dist/types/issue/file.js +6 -0
  325. package/dist/types/issue/file.js.map +1 -0
  326. package/dist/types/issue/parse.js +8 -0
  327. package/dist/types/issue/parse.js.map +1 -0
  328. package/dist/types/main-acp.js +13 -0
  329. package/dist/types/main-acp.js.map +1 -0
  330. package/dist/types/main-adapters.js +5 -0
  331. package/dist/types/main-adapters.js.map +1 -0
  332. package/dist/types/main-credential.js +21 -0
  333. package/dist/types/main-credential.js.map +1 -0
  334. package/dist/types/main-doctor.js +6 -0
  335. package/dist/types/main-doctor.js.map +1 -0
  336. package/dist/types/main-http-handler.js +12 -0
  337. package/dist/types/main-http-handler.js.map +1 -0
  338. package/dist/types/main-http.js +5 -0
  339. package/dist/types/main-http.js.map +1 -0
  340. package/dist/types/main-loops.js +5 -0
  341. package/dist/types/main-loops.js.map +1 -0
  342. package/dist/types/main-mcp.js +12 -0
  343. package/dist/types/main-mcp.js.map +1 -0
  344. package/dist/types/main-orchestrator.js +5 -0
  345. package/dist/types/main-orchestrator.js.map +1 -0
  346. package/dist/types/main-reconcilers.js +11 -0
  347. package/dist/types/main-reconcilers.js.map +1 -0
  348. package/dist/types/main-runner.js +13 -0
  349. package/dist/types/main-runner.js.map +1 -0
  350. package/dist/types/main-startup.js +5 -0
  351. package/dist/types/main-startup.js.map +1 -0
  352. package/dist/types/main-substrates.js +5 -0
  353. package/dist/types/main-substrates.js.map +1 -0
  354. package/dist/types/mcp/dispatch.js +4 -0
  355. package/dist/types/mcp/dispatch.js.map +1 -0
  356. package/dist/types/mcp/post-move.js +7 -0
  357. package/dist/types/mcp/post-move.js.map +1 -0
  358. package/dist/types/mcp.js +9 -0
  359. package/dist/types/mcp.js.map +1 -0
  360. package/dist/types/ports.js +12 -0
  361. package/dist/types/ports.js.map +1 -0
  362. package/dist/types/reconcile/image-decide.js +5 -0
  363. package/dist/types/reconcile/image-decide.js.map +1 -0
  364. package/dist/types/reconcile/ledger.js +7 -0
  365. package/dist/types/reconcile/ledger.js.map +1 -0
  366. package/dist/types/reconcile/pr-loop.js +8 -0
  367. package/dist/types/reconcile/pr-loop.js.map +1 -0
  368. package/dist/types/reconcile/vm-reap.js +8 -0
  369. package/dist/types/reconcile/vm-reap.js.map +1 -0
  370. package/dist/types/reconcile/workspace-decide.js +7 -0
  371. package/dist/types/reconcile/workspace-decide.js.map +1 -0
  372. package/dist/types/reconcile.js +9 -0
  373. package/dist/types/reconcile.js.map +1 -0
  374. package/dist/types/runlog.js +7 -0
  375. package/dist/types/runlog.js.map +1 -0
  376. package/dist/types/runner/actions-runner.js +12 -0
  377. package/dist/types/runner/actions-runner.js.map +1 -0
  378. package/dist/types/runner/gondolin-dispatch.js +5 -0
  379. package/dist/types/runner/gondolin-dispatch.js.map +1 -0
  380. package/dist/types/runner/injection.js +6 -0
  381. package/dist/types/runner/injection.js.map +1 -0
  382. package/dist/types/runner/runner-loop.js +5 -0
  383. package/dist/types/runner/runner-loop.js.map +1 -0
  384. package/dist/types/runner/turn.js +4 -0
  385. package/dist/types/runner/turn.js.map +1 -0
  386. package/dist/types/runner/vm-plan.js +4 -0
  387. package/dist/types/runner/vm-plan.js.map +1 -0
  388. package/dist/types/runtime.js +9 -0
  389. package/dist/types/runtime.js.map +1 -0
  390. package/dist/types/schedule/admission.js +7 -0
  391. package/dist/types/schedule/admission.js.map +1 -0
  392. package/dist/types/schedule/circuit-breaker.js +2 -0
  393. package/dist/types/schedule/circuit-breaker.js.map +1 -0
  394. package/dist/types/schedule/eligibility.js +9 -0
  395. package/dist/types/schedule/eligibility.js.map +1 -0
  396. package/dist/types/schedule/orchestrator-loop.js +10 -0
  397. package/dist/types/schedule/orchestrator-loop.js.map +1 -0
  398. package/dist/types/schedule/sleep-cycle.js +4 -0
  399. package/dist/types/schedule/sleep-cycle.js.map +1 -0
  400. package/dist/types/schedule/slots.js +8 -0
  401. package/dist/types/schedule/slots.js.map +1 -0
  402. package/dist/types/schedule/tick.js +9 -0
  403. package/dist/types/schedule/tick.js.map +1 -0
  404. package/dist/types/server/mcp-runtime.js +8 -0
  405. package/dist/types/server/mcp-runtime.js.map +1 -0
  406. package/dist/types/workflow/parse.js +4 -0
  407. package/dist/types/workflow/parse.js.map +1 -0
  408. package/package.json +22 -10
  409. package/patches/@earendil-works+gondolin+0.12.0.patch +173 -0
  410. package/prompts/Reflect.md +91 -0
  411. package/prompts/Review.md +97 -0
  412. package/prompts/Todo.md +96 -0
  413. package/prompts/_footer.md +41 -0
  414. package/prompts/_preamble.md +42 -0
  415. package/prompts-minimal/Todo.md +26 -0
  416. package/scripts/postinstall.mjs +63 -0
  417. package/scripts/vm-agent.mjs +312 -90
  418. package/WORKFLOW.md +0 -744
  419. package/dist/acp-bridge.js +0 -324
  420. package/dist/acp-bridge.js.map +0 -1
  421. package/dist/actions/cache.js +0 -191
  422. package/dist/actions/cache.js.map +0 -1
  423. package/dist/actions/effects.js +0 -41
  424. package/dist/actions/effects.js.map +0 -1
  425. package/dist/actions/executor.js +0 -570
  426. package/dist/actions/executor.js.map +0 -1
  427. package/dist/actions/index.js +0 -13
  428. package/dist/actions/index.js.map +0 -1
  429. package/dist/actions/parsing.js.map +0 -1
  430. package/dist/actions/predicate-env.js +0 -27
  431. package/dist/actions/predicate-env.js.map +0 -1
  432. package/dist/actions/predicates.js +0 -49
  433. package/dist/actions/predicates.js.map +0 -1
  434. package/dist/actions/templating.js +0 -66
  435. package/dist/actions/templating.js.map +0 -1
  436. package/dist/actions/types.js +0 -15
  437. package/dist/actions/types.js.map +0 -1
  438. package/dist/agent/acp.js +0 -473
  439. package/dist/agent/acp.js.map +0 -1
  440. package/dist/agent/adapter-names.js +0 -159
  441. package/dist/agent/adapter-names.js.map +0 -1
  442. package/dist/agent/adapters.js +0 -511
  443. package/dist/agent/adapters.js.map +0 -1
  444. package/dist/agent/credential-extractors.js +0 -342
  445. package/dist/agent/credential-extractors.js.map +0 -1
  446. package/dist/agent/credential-secrets.js +0 -628
  447. package/dist/agent/credential-secrets.js.map +0 -1
  448. package/dist/agent/credential-ticker.js +0 -57
  449. package/dist/agent/credential-ticker.js.map +0 -1
  450. package/dist/agent/gondolin-creds-staging.js +0 -356
  451. package/dist/agent/gondolin-creds-staging.js.map +0 -1
  452. package/dist/agent/gondolin-dispatch.js +0 -375
  453. package/dist/agent/gondolin-dispatch.js.map +0 -1
  454. package/dist/agent/gondolin.js +0 -124
  455. package/dist/agent/gondolin.js.map +0 -1
  456. package/dist/agent/runner-decisions.js +0 -134
  457. package/dist/agent/runner-decisions.js.map +0 -1
  458. package/dist/agent/runner.js +0 -1456
  459. package/dist/agent/runner.js.map +0 -1
  460. package/dist/agent/tool-call-summary.js +0 -102
  461. package/dist/agent/tool-call-summary.js.map +0 -1
  462. package/dist/agent/vm-acp-mapping.js +0 -73
  463. package/dist/agent/vm-acp-mapping.js.map +0 -1
  464. package/dist/agent/vm-guards.js +0 -262
  465. package/dist/agent/vm-guards.js.map +0 -1
  466. package/dist/agent/vm-port.js +0 -22
  467. package/dist/agent/vm-port.js.map +0 -1
  468. package/dist/agent/vm-process-registry.js +0 -79
  469. package/dist/agent/vm-process-registry.js.map +0 -1
  470. package/dist/bin/cli-args.js +0 -105
  471. package/dist/bin/cli-args.js.map +0 -1
  472. package/dist/errors.js +0 -15
  473. package/dist/errors.js.map +0 -1
  474. package/dist/http-disk.js +0 -135
  475. package/dist/http-disk.js.map +0 -1
  476. package/dist/http-handlers.js.map +0 -1
  477. package/dist/http.js.map +0 -1
  478. package/dist/issues.js +0 -178
  479. package/dist/issues.js.map +0 -1
  480. package/dist/logging.js +0 -203
  481. package/dist/logging.js.map +0 -1
  482. package/dist/mcp.js +0 -706
  483. package/dist/mcp.js.map +0 -1
  484. package/dist/memory.js +0 -85
  485. package/dist/memory.js.map +0 -1
  486. package/dist/orchestrator-decisions.js +0 -331
  487. package/dist/orchestrator-decisions.js.map +0 -1
  488. package/dist/orchestrator.js +0 -1569
  489. package/dist/orchestrator.js.map +0 -1
  490. package/dist/prompt.js +0 -65
  491. package/dist/prompt.js.map +0 -1
  492. package/dist/reconciler/cache.js +0 -65
  493. package/dist/reconciler/cache.js.map +0 -1
  494. package/dist/reconciler/index.js +0 -448
  495. package/dist/reconciler/index.js.map +0 -1
  496. package/dist/reconciler/ledger.js +0 -131
  497. package/dist/reconciler/ledger.js.map +0 -1
  498. package/dist/reconciler/pr-adapters.js +0 -174
  499. package/dist/reconciler/pr-adapters.js.map +0 -1
  500. package/dist/reconciler/pr-decide.js.map +0 -1
  501. package/dist/reconciler/pr.js +0 -422
  502. package/dist/reconciler/pr.js.map +0 -1
  503. package/dist/reconciler/types.js +0 -12
  504. package/dist/reconciler/types.js.map +0 -1
  505. package/dist/reconciler/vm.js +0 -243
  506. package/dist/reconciler/vm.js.map +0 -1
  507. package/dist/reconciler/workspace-defaults.js +0 -83
  508. package/dist/reconciler/workspace-defaults.js.map +0 -1
  509. package/dist/reconciler/workspace.js +0 -272
  510. package/dist/reconciler/workspace.js.map +0 -1
  511. package/dist/runlog.js +0 -403
  512. package/dist/runlog.js.map +0 -1
  513. package/dist/scaffold.js +0 -165
  514. package/dist/scaffold.js.map +0 -1
  515. package/dist/trackers/local.js +0 -445
  516. package/dist/trackers/local.js.map +0 -1
  517. package/dist/trackers/types.js +0 -10
  518. package/dist/trackers/types.js.map +0 -1
  519. package/dist/types.js +0 -3
  520. package/dist/types.js.map +0 -1
  521. package/dist/util/clock.js +0 -12
  522. package/dist/util/clock.js.map +0 -1
  523. package/dist/util/crypto.js +0 -25
  524. package/dist/util/crypto.js.map +0 -1
  525. package/dist/util/frontmatter.js +0 -70
  526. package/dist/util/frontmatter.js.map +0 -1
  527. package/dist/util/fs-issues.js +0 -22
  528. package/dist/util/fs-issues.js.map +0 -1
  529. package/dist/util/process.js +0 -152
  530. package/dist/util/process.js.map +0 -1
  531. package/dist/util/workspace-key.js +0 -10
  532. package/dist/util/workspace-key.js.map +0 -1
  533. package/dist/workflow-loader.js +0 -147
  534. package/dist/workflow-loader.js.map +0 -1
  535. package/dist/workflow.js +0 -822
  536. package/dist/workflow.js.map +0 -1
  537. package/dist/workspace-types.js +0 -8
  538. package/dist/workspace-types.js.map +0 -1
  539. package/dist/workspace.js +0 -443
  540. package/dist/workspace.js.map +0 -1
package/WORKFLOW.yaml ADDED
@@ -0,0 +1,487 @@
1
+ # WORKFLOW.yaml — symphony dispatched against smol-symphony itself.
2
+ #
3
+ # Run with:
4
+ #
5
+ # npx symphony WORKFLOW.yaml
6
+ #
7
+ # The per-issue workspace clones from this repo's `.git` directory and the agent
8
+ # has no network credentials. The remote PR flow is configured in-file via
9
+ # `workspace.github_repo` below (dizk/smol-symphony): on a terminal transition
10
+ # the Done-state actions push the per-issue branch and open a PR. `gh` on the
11
+ # host must be authenticated (`gh auth status` clean); the token never enters
12
+ # the VM.
13
+ #
14
+ # For a fully local setup (branch left in the workspace until cleanup, nothing
15
+ # pushed), set `workspace.github_repo: none`. The SYMPHONY_REPO env var still
16
+ # overrides the in-file value when exported.
17
+ #
18
+ # Every section and option is documented in WORKFLOW.template.yaml.
19
+
20
+ # Declared workflow states. Drives dispatch eligibility (role: active),
21
+ # terminal cleanup (role: terminal), and the propose_issue landing directory
22
+ # (role: holding). This map is the single source of truth — there are no
23
+ # separate active/terminal lists to keep in sync.
24
+ #
25
+ # Per-state `adapter` / `model` / `max_turns` override the workflow-level
26
+ # `acp.*` and `agent.max_turns` defaults at dispatch time, and `max_concurrent`
27
+ # caps how many agents run at once in this state (the global
28
+ # `agent.max_concurrent_agents` stays the cross-state host ceiling).
29
+ # `allowed_transitions` narrows the targets the agent can pass to
30
+ # `symphony.transition` while operating in this state (omit for "any declared
31
+ # state is reachable").
32
+ states:
33
+ Todo:
34
+ role: active
35
+ # Per-state prompt body. The shell loader wraps this file in the shared
36
+ # `prompt.preamble_file` + `prompt.footer_file` (below) and renders it for
37
+ # Todo dispatches — replacing the old inline `{% case issue.state %}` branch.
38
+ prompt_file: prompts/Todo.md
39
+ adapter: claude
40
+ # Opus 4.8, 1M-context variant. The plain `claude-opus-4-7` is the 200K
41
+ # variant — a γ-class refactor dispatch hit two mid-turn compactions before
42
+ # reaching the edit phase; `[1m]` gives ~30x headroom and removes the stall.
43
+ # The suffix is Claude Code's model-selection convention, forwarded to the
44
+ # adapter via ANTHROPIC_MODEL. Review stays on codex (cross-model review).
45
+ model: claude-opus-4-8[1m]
46
+ max_turns: 10
47
+ # Per-state concurrency cap (issue 137): at most one implementer agent at a
48
+ # time. Lives on the state now, symmetric with max_turns; the global
49
+ # `agent.max_concurrent_agents` below remains the cross-state host ceiling.
50
+ max_concurrent: 1
51
+ Review:
52
+ # Codex picks up the implementer's branch and approves or rejects. On
53
+ # approval it transitions the issue to Done with PR-body notes; on
54
+ # rejection it transitions back to Todo with rework instructions.
55
+ role: active
56
+ prompt_file: prompts/Review.md
57
+ adapter: codex
58
+ # codex-acp accepts the model via `-c model="..."` argv (TOML); see
59
+ # src/agent/adapters.ts. `gpt-5-codex` was historically rejected with the
60
+ # ChatGPT-account user, so leave `model` unset and let codex-acp pick its
61
+ # own default code-review model. Operators with an API-key Codex setup can
62
+ # pin a specific model here once that's known-good.
63
+ max_turns: 6
64
+ allowed_transitions: [Todo, Done]
65
+ Reflect:
66
+ # Sleep cycle (issue 122) as a recurring STATE that SPAWNS ephemeral
67
+ # reflection issues — not one immortal ticket. When the `spawn:` trigger below
68
+ # fires, the orchestrator MINTS a fresh issue into Reflect; it runs once and
69
+ # terminates in `Reflected` (a per-cycle audit trail), like every other
70
+ # ticket. eval_mode binds the read-only /symphony/issues (all state dirs,
71
+ # including the Done/*.md handoff transcripts) and /symphony/logs (per-issue
72
+ # JSONL run logs) mounts so the agent can mine finished work for *recurring*
73
+ # harness friction, distil lessons, and file improvement proposals via
74
+ # propose_issue (which land in Triage — the human gate). It reflects on *how
75
+ # symphony runs work* (the WORKFLOW.yaml `prompt_file` bodies under prompts/,
76
+ # per-state model/max_turns/effort/actions, the gondolin image config,
77
+ # acceptance criteria, timeouts), NOT the product code under review. See the
78
+ # Reflect prompt file (prompts/Reflect.md) for the read → distil → propose
79
+ # loop and the guardrails. Cadence: the operator / an external cron / a real
80
+ # dashboard "reflect now" button can mint a cycle by creating a Reflect issue,
81
+ # and the orchestrator auto-spawns on idle or after N terminal transitions
82
+ # (see this state's `spawn:` block below).
83
+ role: active
84
+ prompt_file: prompts/Reflect.md
85
+ adapter: claude
86
+ # 1M-context Opus: a reflection turn reads many Done/*.md transcripts plus
87
+ # the relevant logs/<id>.jsonl, so the large-context variant avoids mid-turn
88
+ # compaction (same rationale as the Todo state).
89
+ model: claude-opus-4-8[1m]
90
+ # Higher than Todo/Review: reading the history, distilling patterns, and
91
+ # filing one proposal per lesson takes more turns than a single edit/review.
92
+ max_turns: 20
93
+ # Bind the read-only /symphony/issues + /symphony/logs mounts for this state.
94
+ eval_mode: true
95
+ # The reflector may ONLY terminate in Reflected — it cannot route itself into
96
+ # Todo/Review/Done. A guardrail on this self-modifying loop; filing
97
+ # improvements happens through propose_issue (→ Triage), which is
98
+ # independent of allowed_transitions. Reflected is a DEDICATED terminal (NOT
99
+ # Done): Done carries the PR autopilot actions, and a minted reflection lands
100
+ # on an empty branch — terminating in Done would try to open a PR off it.
101
+ allowed_transitions: [Reflected]
102
+ # Recurring spawn trigger. This active state mints a FRESH ephemeral
103
+ # reflection issue into itself: `on_idle` when the orchestrator is idle and
104
+ # >=1 issue reached a terminal state since the last cycle, and `after_terminal`
105
+ # as a backstop once that many issues have reached a terminal state since the
106
+ # last cycle. The terminal-transition counter resets to 0 on a successful mint.
107
+ # `max_in_flight: 1` is load-bearing — it replaces the immortal ticket's
108
+ # built-in "one location = one reflection" mutex, so a busy stretch never mints
109
+ # a second cycle while one is still live. `title` stamps the mint time
110
+ # (`{{ stamp }}`); the generated body carries the trigger + counter provenance.
111
+ # GUARDRAILS (carried over from issue 122): spawning ONLY mints the reflection
112
+ # — the proposals it files still land in Triage and still require human
113
+ # approve/discard, so this does not bypass the human gate.
114
+ spawn:
115
+ on_idle: true
116
+ after_terminal: 10
117
+ title: "Reflection {{ stamp }}"
118
+ max_in_flight: 1
119
+ Done:
120
+ role: terminal
121
+ # PR autopilot routing (issue 38; moved onto the state in issue 139). Done
122
+ # is the merge state: a MERGEABLE Done-state PR has GitHub auto-merge armed
123
+ # with `squash` (matches the repo's `NN: title (#PR)` history); a
124
+ # CONFLICTING one is routed back to `on_conflict.route_to` (Todo) for the
125
+ # dispatched agent to rebase. The host-global on/off switch + poll TTL live
126
+ # in the top-level `pr:` block below; the merge/close/route targets are
127
+ # derived by scanning states for this `pr:` field (no named-string sibling
128
+ # block). While the engine is enabled, transitions into Done no longer fire
129
+ # the standard terminal workspace cleanup — the pr resource owns the
130
+ # workspace until its PR merges or closes.
131
+ pr:
132
+ auto_merge: squash
133
+ on_conflict:
134
+ route_to: Todo
135
+ # Issue 36 (reconciler v2 / typed action DAG): the legacy `after_run`
136
+ # shell that pushed the branch and opened a PR is replaced by two typed
137
+ # actions. The host pre-stages SYMPHONY_PR_TITLE / SYMPHONY_PR_BODY_FILE /
138
+ # SYMPHONY_BRANCH (the same values the old shell read); the action
139
+ # executor exposes them as $pr_title / $pr_body_file / $branch /
140
+ # $base_branch / $repo in the fixed template namespace
141
+ # (src/actions/types.ts → ActionContext). The `if: $repo` predicate
142
+ # matches the old `[ -n "${SYMPHONY_REPO:-}" ] || exit 0` short-circuit
143
+ # so the local-only mode is still a no-op. Per-action retry/snapshot
144
+ # plumbing replaces the opaque shell-exit-code surface; on rate-limit
145
+ # the create_pr_if_missing action shows "retrying in 60s" on the
146
+ # dashboard instead of a silent failure.
147
+ #
148
+ # on_error route (issue 235): a Done handoff that fails — `push_branch`
149
+ # server-side-declined (e.g. a `.github/workflows/` edit the Workflows:write-less
150
+ # PAT can't push), or `create_pr_if_missing` erroring after its retries — must
151
+ # NOT dead-end silently in terminal Done with no branch on the remote and no PR.
152
+ # Each action reroutes the issue OUT of Done into the `HandoffFailed` holding
153
+ # state (below) with the failure reason appended, so the stranded handoff is
154
+ # operator-visible on the dashboard instead of surfacing only as a log line. The
155
+ # per-issue workspace is retained (issue 234's handoff-pending marker) so the
156
+ # completed commit survives for a manual push. The default retry (count 3) still
157
+ # applies first — a transient `create_pr_if_missing` rate-limit recovers without
158
+ # routing; only a still-failing handoff reroutes.
159
+ actions:
160
+ - kind: push_branch
161
+ name: push-branch
162
+ remote: origin
163
+ ref: $branch
164
+ if: $repo
165
+ on_error:
166
+ then: { route_to: HandoffFailed }
167
+ - kind: create_pr_if_missing
168
+ name: open-pr
169
+ base: $base_branch
170
+ head: $branch
171
+ title_from: $pr_title
172
+ body_from: $pr_body_file
173
+ if: $repo
174
+ on_error:
175
+ then: { route_to: HandoffFailed }
176
+ Cancelled:
177
+ role: terminal
178
+ # Cancelled means the work was abandoned; no patch, no PR. The workspace is
179
+ # cleaned up after the run unwinds and the commits are discarded with it.
180
+ # PR autopilot close state (issue 139): when the engine is enabled, an open
181
+ # PR for a Cancelled issue is closed without merge and its remote branch is
182
+ # best-effort-deleted. Unlike the merge state, the close path needs no
183
+ # workspace, so standard terminal cleanup still runs on transition in.
184
+ pr:
185
+ close: true
186
+ Reflected:
187
+ # Dedicated terminal for a completed sleep-cycle reflection (replaces the old
188
+ # Dormant parking state). Each minted reflection issue lands here after filing
189
+ # its proposals, so the column is a browsable per-cycle audit trail — one issue
190
+ # per run — instead of one ever-growing immortal ticket. Deliberately carries
191
+ # NO `pr:` and NO `actions:`: a reflection runs on an empty branch (it only
192
+ # files propose_issue → Triage), so the Done-state PR autopilot must NOT fire
193
+ # — that's the whole reason this is a separate terminal from Done. Standard
194
+ # terminal workspace cleanup runs on transition in.
195
+ role: terminal
196
+ Triage:
197
+ # Landing directory for `symphony.propose_issue`. Never dispatched; the
198
+ # operator approves or discards from the dashboard. Declared FIRST among
199
+ # holding states so it stays the `propose_issue` landing + triage target
200
+ # (both resolve the first declared holding state).
201
+ role: holding
202
+ HandoffFailed:
203
+ # Landing state for a Done handoff that FAILED (issue 235). The agent already
204
+ # transitioned the issue to Done, but the Done `actions:` handoff (push_branch /
205
+ # create_pr_if_missing) couldn't land — most commonly a `.github/workflows/`
206
+ # edit the shared fine-grained PAT can't push (Workflows:write is intentionally
207
+ # withheld; that diff is a deliberate manual SSH-push step). Rather than dead-end
208
+ # invisibly in terminal Done with no branch on the remote and no PR, the failing
209
+ # action's `on_error.then.route_to` (states.Done.actions, above) reroutes the
210
+ # issue here with the failure reason appended to its body. Holding → never
211
+ # dispatched: it parks for an operator to push the `agent/<id>` branch by hand
212
+ # (the per-issue workspace is RETAINED by issue 234's handoff-pending marker, so
213
+ # the completed commit survives) and then move the issue back to Done, or to
214
+ # Cancelled. Declared AFTER Triage so Triage stays the `propose_issue` landing
215
+ # (the first declared holding state).
216
+ role: holding
217
+
218
+ # Shared prompt-assembly files (per-state prompt split). Each active state above
219
+ # names its own `prompt_file`; the loader wraps that body in this shared header
220
+ # and footer and renders the result for that state — so the config above reads as
221
+ # the shape of the workflow, and each prompt is edited in its own file under
222
+ # `prompts/` instead of one 800-line `{% case issue.state %}`. Both paths are
223
+ # resolved relative to this file. There is no inline prompt body anymore —
224
+ # workflow files are pure YAML. A dispatched state that declares no `prompt_file`
225
+ # (or omitting this block) falls back to a generic built-in prompt
226
+ # ("You are working on an issue."), so every active state should name one.
227
+ prompt:
228
+ preamble_file: prompts/_preamble.md
229
+ footer_file: prompts/_footer.md
230
+
231
+ tracker:
232
+ kind: local
233
+ # Operator-scoped tracker root (outside the repo). State transitions and
234
+ # propose_issue writes don't dirty the codebase's git status. Symphony
235
+ # auto-mkdirs every declared state directory under this root on startup.
236
+ root: ~/.symphony/trackers/smol-symphony
237
+
238
+ # PR autopilot engine toggle (issue 38, simplified by issue 101; routing moved
239
+ # onto states in issue 139). This is the slim host-global half only — the
240
+ # on/off switch and the per-PR `gh pr view` cache TTL. The merge/close/route
241
+ # targets and the auto-merge strategy now live ON the states they describe:
242
+ # `states.Done.pr` (merge state — auto_merge + on_conflict.route_to) and
243
+ # `states.Cancelled.pr` (close state — close: true), above. The reconciler
244
+ # derives those by scanning states; there is no named-string sibling block.
245
+ #
246
+ # Enabled 2026-05-25 so MERGEABLE Done-state PRs have GitHub auto-merge armed
247
+ # and CONFLICTING ones are routed back to Todo for the dispatched agent to
248
+ # rebase (the host runs `git fetch origin <base>` before each dispatch so
249
+ # `origin/<base>` is current, and the Todo prompt's first step is
250
+ # `git rebase origin/<base>`). There is no autopilot-side rebase machinery and
251
+ # no consecutive-failure circuit breaker.
252
+ #
253
+ # PREREQUISITE: `gh pr merge --auto` requires at least one branch-protection
254
+ # rule on `main`, or arming auto-merge errors. Ensure one exists in the repo's
255
+ # GitHub settings. To disable, set `enabled: false` (the resource is then never
256
+ # constructed and Done-state behavior reverts to the actions-block PR-create +
257
+ # operator merge).
258
+ pr:
259
+ enabled: true
260
+ poll_interval_ms: 30000
261
+
262
+ polling:
263
+ interval_ms: 5000
264
+
265
+ # The canonical clone + base-branch checkout + `agent/<id>` branch cut +
266
+ # origin/identity setup is owned by the orchestrator's TypeScript
267
+ # `setupWorkspaceDir` action (issue 34 / reconciler stage 3) — there is no
268
+ # shell `hooks:` surface. The per-issue workspace arrives at the dispatched
269
+ # agent with:
270
+ #
271
+ # • a hardlinked `git clone --local` of the source repo on the base branch
272
+ # (`workspace.base_branch`, or the SYMPHONY_BASE_BRANCH env override,
273
+ # default `main`) at the source repo's current local base SHA
274
+ # • all network remotes stripped (in-VM `git push`/`git fetch` fail closed)
275
+ # • when `workspace.github_repo` (or the SYMPHONY_REPO env override) is set:
276
+ # `origin` restored to the canonical HTTPS URL so the host's Done-state
277
+ # `push_branch` action can push (`gh auth setup-git` runs best-effort on the
278
+ # host so the push has credentials; the token never enters the VM)
279
+ # • `user.name = symphony-agent` / `user.email = agent@symphony.local`
280
+ # • `agent/<id>` checked out
281
+ #
282
+ # The source repo's local `<base>` is the single source of truth for the
283
+ # workspace's base ref. To pick up a new base, update the source repo
284
+ # (`git pull` / `git fetch && git checkout <base>`) before the next dispatch;
285
+ # symphony does not implicitly fetch from `origin/<base>` at setup time.
286
+ #
287
+ # Need extra per-VM tooling on top of that? Bake it into the agent image
288
+ # (`images/agents/`), or run arbitrary guest commands from a state's `actions:`
289
+ # via `run_in_vm`. The post-attempt push + PR-create handoff is the Done
290
+ # state's `actions:` block above.
291
+ workspace:
292
+ # Explicit in-repo workspace root (the new unset default is
293
+ # ~/.symphony/workspaces/<project>). Kept explicit here so in-flight per-issue
294
+ # working trees aren't relocated mid-burndown; drop this line to inherit home.
295
+ root: ./.symphony/workspaces
296
+ # PR/push target for the dogfood (symphony-on-symphony) setup: the Done-state
297
+ # actions push the per-issue branch + open a PR against this repo. Set to
298
+ # `none` for local-only (branch left in the workspace). The SYMPHONY_REPO env
299
+ # var still overrides this if exported.
300
+ github_repo: dizk/smol-symphony
301
+ # Branch the per-issue workspace clones from + targets as the PR base. The
302
+ # SYMPHONY_BASE_BRANCH env var still overrides this if exported.
303
+ base_branch: main
304
+
305
+ # Per-issue JSONL run logs plus an orchestrator-side `symphony.log` mirror.
306
+ # One JSONL file per issue, appended across attempts and process restarts;
307
+ # captures every ACP JSON-RPC frame to/from the VM, raw adapter stderr,
308
+ # typed-action output, and orchestrator lifecycle events — intended for
309
+ # later evaluation by another agent. The sibling `symphony.log` captures the
310
+ # orchestrator's structured log (dispatch, actions, reconciler, shutdown) in the
311
+ # same `key=value` format so a post-hoc review has both surfaces in one
312
+ # directory. While the file sink is active the console shows only the startup
313
+ # banner; `tail -f symphony.log` follows the detail, and `--verbose` mirrors it
314
+ # back to the console. See WORKFLOW.template.yaml for the full schema.
315
+ logs:
316
+ # Operator-scoped run-log root (outside the repo), matching the home default
317
+ # (~/.symphony/logs/<project>) and the operator-scoped tracker.root above.
318
+ # Durable, expensive-to-rebuild run logs survive a delete / re-clone of the repo
319
+ # instead of living inside the working tree. (Leaving this unset would inherit
320
+ # the same path, since github_repo derives <project> = smol-symphony.)
321
+ root: ~/.symphony/logs/smol-symphony
322
+
323
+ agent:
324
+ # SERIALIZED to 1 (2026-05-27) to stop the FC/IS burn-down conflict storm:
325
+ # every burn-down PR edits the same policy files (package.json --max-warnings
326
+ # ratchet, .dependency-cruiser.cjs, eslint.config.js), so any two in flight
327
+ # conflict by construction. Serial dispatch makes each PR rebase on the prior
328
+ # merge. Revert to 2 once the arch-burndown queue drains.
329
+ max_concurrent_agents: 1
330
+ max_turns: 6
331
+ max_retry_backoff_ms: 120000
332
+
333
+ acp:
334
+ # Selecting "claude" is enough: symphony probes ~/.claude/.credentials.json
335
+ # on the host at startup and auto-generates a launch command for the in-VM
336
+ # agent. There is no `command` escape hatch under the ACP WebSocket transport —
337
+ # the launch shape is fixed; fork scripts/vm-agent.mjs if you need to customize
338
+ # what the agent spawns.
339
+ adapter: claude
340
+ # Credentials never enter the VM (issue 113; codex generalized in 116). The
341
+ # guest holds only a token-shaped placeholder; the host substitutes the real
342
+ # upstream credential into the outbound request at Gondolin egress (TLS-MITM):
343
+ # for claude, the Anthropic OAuth access token; for codex (the Review state's
344
+ # adapter), the OpenAI credential read from ~/.codex/auth.json (access token or
345
+ # OPENAI_API_KEY, never the refresh token). Every credential-bearing var is
346
+ # stripped from the forwarded VM boot env, so no real credential lands in the VM.
347
+ # Reasoning effort forwarded to claude-agent-acp via a staged settings.json
348
+ # (`{"effortLevel": "xhigh"}`) copied into /root/.claude/settings.json before the
349
+ # adapter starts. xhigh is the second-highest tier under Opus 4.7 (max is the top
350
+ # but is meaningfully slower); operators on a Haiku-backed model must drop this
351
+ # because Haiku rejects xhigh at adapter startup. Valid set is `low|medium|high|xhigh|max`,
352
+ # model-gated by claude-agent-acp's `supportedEffortLevels`.
353
+ effort: xhigh
354
+ shell: bash
355
+ # Hard cap on a single session/prompt regardless of activity. Raised from 30min to
356
+ # 60min (the code default) because a heavy refactor turn at effort=xhigh can run a
357
+ # single uninterrupted turn past 30min — issue 103's healthy attempt was killed
358
+ # mid-edit at the old 1800000 cap with turns_completed:0. Distinct from
359
+ # stall_timeout_ms below (which only trips on NO activity).
360
+ prompt_timeout_ms: 3600000
361
+ read_timeout_ms: 30000
362
+ # ACP rides the unified HTTP server: the in-VM agent (the injected launcher at
363
+ # `/opt/symphony/vm-agent.mjs` — see the BYO-image contract under `gondolin:`) dials
364
+ # back the `/acp` WebSocket through the SAME `tcp.hosts` tunnel MCP uses
365
+ # (`ws://symphony-mcp:7001/acp`) and authenticates with a per-dispatch bearer sent as the
366
+ # first WebSocket message. No separate ACP listener / bind port — the raw-TCP bridge is
367
+ # retired. `connect_timeout_ms` bounds how long to wait for that dial-back (default 30000).
368
+ # connect_timeout_ms: 30000
369
+ # ACP-over-WebSocket liveness heartbeat. Both ends of the `/acp` socket send an
370
+ # application-level ping every `heartbeat_interval_ms` and tear the socket down (a typed
371
+ # `transport_error`, which feeds the consecutive circuit breaker — NOT a refusal) if no
372
+ # inbound traffic arrives for `heartbeat_timeout_ms`. This surfaces a half-open socket in
373
+ # seconds instead of waiting for `prompt_timeout_ms` or the stall reaper. The heartbeat is
374
+ # INDEPENDENT of ACP frames, so a long silent tool call stays alive; the conservative
375
+ # defaults (interval 15000 / timeout 45000 ≈ 3 intervals) keep a slow KVM host from
376
+ # false-positiving a live-but-quiet peer mid-turn. Raise the timeout if a heavily loaded
377
+ # host legitimately pauses the guest event loop past 45s.
378
+ # heartbeat_interval_ms: 15000
379
+ # heartbeat_timeout_ms: 45000
380
+ # Time between any ACP event from the adapter before symphony kills the attempt as stalled.
381
+ # Raised from the 5-minute default because Opus 4.7 at effort=xhigh can take many minutes to
382
+ # produce its first thought chunk on a heavy prompt. If a real wedge happens, attempts will
383
+ # die at this longer threshold; if the agent is just thinking, we let it finish.
384
+ stall_timeout_ms: 1800000
385
+
386
+ gondolin:
387
+ # Per-issue microVM (Gondolin substrate). `image` is the agent rootfs the VM
388
+ # boots. The value is a Gondolin image selector: a content-addressed build id, a
389
+ # `name:tag` ref like `symphony-agents:latest`, or a path to an exported asset
390
+ # directory. Build the reference image with `npm run build:image` (see images/agents/).
391
+ #
392
+ # IMAGE CONTRACT (issue 209 — mise-only base): the image ships ONLY the `mise`
393
+ # binary (+ glibc essentials). node + the agent CLIs are NOT baked — they come from a
394
+ # mise SYSTEM config symphony STAGES at /etc/mise/config.toml at dispatch
395
+ # (assets/symphony-mise.system.toml), installed via `mise install` into a
396
+ # warm-cached data dir (see `mise:` below). The in-VM launcher (`scripts/vm-agent.mjs`)
397
+ # is likewise INJECTED at dispatch (read off the host's own disk, SHA-256 logged), so
398
+ # neither a CLI bump nor a launcher/transport change needs an image rebuild + repin.
399
+ # A pre-launch probe fails fast with a precise error (not an opaque ENOENT) if unmet.
400
+ #
401
+ # The image MUST provide ONLY:
402
+ # 1. the `mise` binary on PATH (node + the agent CLIs are mise-installed; the agent
403
+ # runtime node is resolved system-scoped via `mise which node`, kept ≥ 21 for the
404
+ # launcher's global WebSocket). mise provisioning is the ONLY toolchain path
405
+ # (issue 233) — there is no opt-out, so the base image need not bake node;
406
+ # 2. /bin/sh + base64 + mkdir/chmod (coreutils — present in any Debian/Alpine base)
407
+ # for the staging write;
408
+ # 3. a writable rootfs overlay (Gondolin default `cow`; a deliberately
409
+ # readonly-manifest image transparently falls back to a /tmp tmpfs path);
410
+ # 4. distro CA files left intact (Gondolin injects its MITM CA at boot — the image
411
+ # must not shadow /etc/ssl).
412
+ # Symphony injects everything else: the mise SYSTEM config, the launcher, the
413
+ # bind-mount-trust gitconfig, the fake-cred placeholder files, and all SYMPHONY_* /
414
+ # PATH / GIT_CONFIG_GLOBAL launch env.
415
+ #
416
+ # The agent-CLI pins live in assets/symphony-mise.system.toml (claude-code,
417
+ # codex, claude-agent-acp, codex-acp) — bump THERE + restart, no rebuild.
418
+ # mise-only base (issue 209). #2 (PR #211) installs the agent toolchain onto the guest
419
+ # ROOTFS at dispatch — sandboxfs silently drops chmod, so an executable can't be born on
420
+ # the bind mount; the persistent download cache stays on the bind mount (data only).
421
+ # `rootfs_size` grows the ephemeral install fs past the ~593 MB default. Bump an agent CLI
422
+ # in assets/symphony-mise.system.toml + restart — no image rebuild (only a base/mise bump needs
423
+ # `npm run build:image` + a repin here). NB the codex reviewer egress needed the
424
+ # chatgpt-backend route swap scoped to the platform host (credential-hooks.ts) to reach
425
+ # npm/nodejs — see docs/research/codex-route-hook-host-rewrite-rca.md.
426
+ rootfs_size: 3G
427
+ image: e8a1562b-ea57-54c6-9f0a-ea9aa6f1d40d
428
+ cpus: 2
429
+ mem_mib: 4096
430
+ # Absolute guest path the injected launcher is staged + exec'd at. Default
431
+ # `/opt/symphony/vm-agent.mjs` (the historical bake path). The same value is the write
432
+ # target AND the exec arg, so they can never drift; a readonly-rootfs image falls back
433
+ # to a /tmp path automatically. Override only for an unusual image layout.
434
+ # guest_agent_path: /opt/symphony/vm-agent.mjs
435
+ # The node binary the launcher is exec'd with. Default bare `node` (the pre-launch
436
+ # probe resolves it to an absolute path so the exec is PATH-independent). Set this for
437
+ # an image that ships node under a non-PATH name (e.g. /opt/node/bin/node).
438
+ # node_bin: node
439
+ # No runtime bind-mounts. The launcher is INJECTED (staged per-dispatch), not mounted,
440
+ # so it needs no `volumes` entry. Keeping `volumes` empty leaves room for an eval_mode
441
+ # state's two read-only mounts (/symphony/issues + /symphony/logs) on top of the
442
+ # auto-mounted workspace. Credentials never mount: the host substitutes the real token
443
+ # at Gondolin egress; the tracker is reached via the symphony MCP server (or the
444
+ # eval_mode mount).
445
+ volumes: []
446
+ # forward_env is a generic passthrough into the VM boot env, but the runner
447
+ # strips EVERY credential-bearing var before boot (the guest holds only a
448
+ # placeholder Gondolin substitutes at egress) — so listing OPENAI_API_KEY here
449
+ # does NOT plant the real key in a VM.
450
+ forward_env:
451
+ - OPENAI_API_KEY
452
+ - ANTHROPIC_API_KEY
453
+
454
+ egress:
455
+ # General dev-tooling firewall for the in-VM agent. Gondolin denies guest egress
456
+ # by default; the agent can always reach its own inference host (handled by the
457
+ # credential layer), and these hosts are additionally opened so gates can run
458
+ # (`npm install`, git-based deps, release binaries). SECURITY: nothing here ever
459
+ # gets a real token substituted — listing a host grants plain network egress
460
+ # only. The real upstream token is substituted solely on each adapter's inference
461
+ # host (see src/agent/credential-secrets.ts).
462
+ allowed_hosts:
463
+ - nodejs.org # mise node prebuilts (issue 209)
464
+ - registry.npmjs.org # npm install + mise npm: backend (the agent CLIs)
465
+ - mise.jdx.dev # mise registry redirect (issue 209)
466
+ - mise-versions.jdx.dev # mise precompiled tool version lists (node@N, etc.; issue 223)
467
+ - github.com # git-based deps / release pages
468
+ - codeload.github.com # GitHub tarball fetch
469
+ - objects.githubusercontent.com # release-binary downloads
470
+
471
+ server:
472
+ # Pinned to 8787 for stable local/tailscale access (bookmarks, forwards,
473
+ # scripts). #160 made the port ephemeral by default — so two instances never
474
+ # collide on the HTTP port — and banners the bound port. This project runs a
475
+ # single instance, so a fixed, predictable port is preferable. Omit `port:`
476
+ # (or pass `--port N`) to return to ephemeral. Takes effect on next restart.
477
+ port: 8787
478
+ # Bound to all interfaces because access is gated by tailscale, not by the
479
+ # HTTP server itself. The endpoint has no auth; only expose it inside a
480
+ # trusted network boundary.
481
+ host: 0.0.0.0
482
+
483
+ mcp:
484
+ # Gondolin maps a synthetic guest host to the host's loopback (`tcp.hosts`), so
485
+ # 127.0.0.1 from inside the VM hits the host's listener. Override only if
486
+ # your VMM has a different host alias.
487
+ host: 127.0.0.1
@@ -0,0 +1,136 @@
1
+ ---
2
+ name: symphony-issues
3
+ description: Author well-formed issues for a symphony tracker, and decompose a large task into a blocked_by DAG of small one-PR issues. Trigger when asked to "file a symphony issue", "open/create a tracker issue", "break this task into issues", or "decompose into a DAG of issues".
4
+ ---
5
+
6
+ # Filing symphony issues
7
+
8
+ This project is driven by **symphony**: a coding agent picks up one tracker
9
+ issue, works it on a per-issue branch, and opens exactly one PR. **The issue
10
+ body IS the dispatched agent's prompt** — write every issue for that reader.
11
+
12
+ Anything project-specific (the HTTP port, the tracker root, the state names)
13
+ lives in this repo's `WORKFLOW.yaml`. Read it from there; this skill never
14
+ hardcodes it.
15
+
16
+ ## The four-section issue body
17
+
18
+ Every issue body uses the same four sections, in this order:
19
+
20
+ - **Problem** — what's wrong and why now. The motivation the agent needs to
21
+ make the judgement calls the Change leaves open.
22
+ - **Change** — the concrete edits. Name file paths and line ranges where you
23
+ know them; describe the *shape* of the change, not just the goal.
24
+ - **allowed_paths** — the workspace scope the dispatched agent is permitted to
25
+ touch, one path per line.
26
+ - **Acceptance** — the checks that must pass before the agent transitions the
27
+ issue to a terminal state. Usually the build/test/lint gate plus the
28
+ issue-specific assertions that prove this change works.
29
+
30
+ Why this shape, plainly: **the body is the prompt, `allowed_paths` is the
31
+ leash, and Acceptance is the definition of done.** A vague body yields a vague
32
+ PR; a missing `allowed_paths` lets the agent wander into unrelated files; a
33
+ missing Acceptance leaves "done" undefined and the reviewer with nothing to
34
+ check against.
35
+
36
+ ## How to file
37
+
38
+ Find `server.port` in this repo's `WORKFLOW.yaml`, then POST JSON to the running
39
+ symphony HTTP server:
40
+
41
+ ```bash
42
+ curl -s -X POST http://127.0.0.1:<server.port>/api/v1/issues \
43
+ -H "content-type: application/json" \
44
+ --data-binary @/tmp/issue.json
45
+ ```
46
+
47
+ Body shape:
48
+
49
+ ```json
50
+ {
51
+ "title": "required, non-empty",
52
+ "state": "Todo",
53
+ "identifier": "108",
54
+ "description": "markdown body — the four sections above, below the front matter",
55
+ "priority": 2,
56
+ "labels": ["refactor"],
57
+ "blocked_by": ["107"]
58
+ }
59
+ ```
60
+
61
+ - `title` is the only required field.
62
+ - `state` is optional — it defaults to the **first active state** declared in
63
+ `WORKFLOW.yaml`. Any declared **non-terminal** state is a valid explicit target:
64
+ both active states and holding states (e.g. a Triage holding state) can be
65
+ created into. Only **terminal** states are closed to direct creation.
66
+ - `identifier` is optional — the server allocates the next free integer.
67
+ - `priority`, `labels`, and `blocked_by` are optional.
68
+ - The server stamps `created_at` / `updated_at` and returns `201` with
69
+ `{ path, identifier, state }`. Duplicate ids / bad states come back `4xx`.
70
+
71
+ **Disk fallback** (server not running): hand-write a file at
72
+ `<tracker.root>/<State>/<n>.md` — read `tracker.root` and the state names from
73
+ your `WORKFLOW.yaml` — with the same YAML front matter (`title`, optional
74
+ `priority` / `labels` / `blocked_by`) and the four-section body below it. The
75
+ **disk is canonical for reading**; the API is just the filing front door, so
76
+ look at neighbouring files under the tracker root for the exact front-matter
77
+ shape this project uses.
78
+
79
+ ## Decomposing a large task into a `blocked_by` DAG
80
+
81
+ This is the high-leverage move, and the reason to reach for this skill. Don't
82
+ file one giant issue — model the task as a **dependency graph of small issues,
83
+ one PR per node.**
84
+
85
+ - `blocked_by: ["<id>", …]` makes an issue **ineligible to dispatch** until
86
+ every blocker has reached a **terminal** tracker state.
87
+ - **Serialize work that shares a file or artifact.** Two agents editing the
88
+ same file in parallel collide and invalidate each other's branch — chain
89
+ those nodes `A ← B ← C` (B `blocked_by` A, C `blocked_by` B) so they land in
90
+ order.
91
+ - **Parallelize only genuinely independent work** (disjoint files / artifacts).
92
+ File those with no `blocked_by` so they fan out across the concurrency cap and
93
+ run at once.
94
+
95
+ ### Two gotchas to design around
96
+
97
+ 1. **Terminal ≠ merged.** `blocked_by` unblocks the moment a blocker hits a
98
+ *terminal tracker state*, which can **precede the blocker's PR actually
99
+ merging**. A downstream node may therefore dispatch off a base branch that
100
+ still lacks the upstream change. Fast-forward / rebase the base between
101
+ merges so each node starts from its blockers' landed code.
102
+ 2. **Keep downstream specs at intent altitude.** Don't pin line numbers in a
103
+ node that won't dispatch until several upstream PRs have landed — they'll
104
+ have drifted by then. Say *"locate by name"* or *"run the relevant check for
105
+ the current count"* instead of `src/foo.ts:412`.
106
+
107
+ ### Worked example: rename an API across many call sites
108
+
109
+ ```
110
+ discover (enumerate every call site)
111
+ ├── transform-call-site-A ─┐
112
+ ├── transform-call-site-B ─┤ (parallel — disjoint files)
113
+ └── transform-call-site-C ─┘
114
+ └── verify (typecheck + test the whole tree)
115
+ ```
116
+
117
+ 1. **`discover`** — one issue that enumerates every call site and lists them in
118
+ its handoff notes. No `blocked_by` — it dispatches immediately.
119
+ 2. **N transform issues** — one per call site, each `blocked_by` the discover
120
+ issue and each touching **disjoint files**, so they run in parallel:
121
+
122
+ ```json
123
+ { "title": "Rename API in module A",
124
+ "blocked_by": ["<discover-id>"], "labels": ["refactor"] }
125
+ ```
126
+ 3. **`verify`** — one issue blocked on *all* the transforms, asserting the whole
127
+ tree still typechecks and tests after every call site moved:
128
+
129
+ ```json
130
+ { "title": "Verify API rename across the tree",
131
+ "blocked_by": ["<A-id>", "<B-id>", "<C-id>"] }
132
+ ```
133
+
134
+ The transforms fan out once `discover` is terminal; `verify` waits for all
135
+ three. Keep `verify`'s spec at intent altitude (gotcha 2) — it dispatches last,
136
+ after the others have drifted the line numbers it would otherwise have pinned.