smol-symphony 0.2.0 → 0.3.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 (716) 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/src/bin/symphony.js +30 -0
  11. package/dist/src/bin/symphony.js.map +1 -0
  12. package/dist/src/core/actions/context.js +109 -0
  13. package/dist/src/core/actions/context.js.map +1 -0
  14. package/dist/{actions/parsing.js → src/core/actions/parse.js} +33 -114
  15. package/dist/src/core/actions/parse.js.map +1 -0
  16. package/dist/src/core/actions/plan.js +197 -0
  17. package/dist/src/core/actions/plan.js.map +1 -0
  18. package/dist/src/core/actions/predicates.js +111 -0
  19. package/dist/src/core/actions/predicates.js.map +1 -0
  20. package/dist/src/core/actions/run-fold.js +248 -0
  21. package/dist/src/core/actions/run-fold.js.map +1 -0
  22. package/dist/src/core/actions/template.js +118 -0
  23. package/dist/src/core/actions/template.js.map +1 -0
  24. package/dist/src/core/cli/args.js +116 -0
  25. package/dist/src/core/cli/args.js.map +1 -0
  26. package/dist/src/core/coerce.js +75 -0
  27. package/dist/src/core/coerce.js.map +1 -0
  28. package/dist/src/core/credential/account-id.js +20 -0
  29. package/dist/src/core/credential/account-id.js.map +1 -0
  30. package/dist/src/core/credential/adapter-config.js +136 -0
  31. package/dist/src/core/credential/adapter-config.js.map +1 -0
  32. package/dist/src/core/credential/availability.js +98 -0
  33. package/dist/src/core/credential/availability.js.map +1 -0
  34. package/dist/src/core/credential/extract.js +228 -0
  35. package/dist/src/core/credential/extract.js.map +1 -0
  36. package/dist/src/core/credential/fake-creds.js +171 -0
  37. package/dist/src/core/credential/fake-creds.js.map +1 -0
  38. package/dist/src/core/credential/identity.js +125 -0
  39. package/dist/src/core/credential/identity.js.map +1 -0
  40. package/dist/src/core/credential/shape.js +230 -0
  41. package/dist/src/core/credential/shape.js.map +1 -0
  42. package/dist/src/core/credential/strings.js +15 -0
  43. package/dist/src/core/credential/strings.js.map +1 -0
  44. package/dist/src/core/doctor/checks.js +303 -0
  45. package/dist/src/core/doctor/checks.js.map +1 -0
  46. package/dist/src/core/git/result.js +107 -0
  47. package/dist/src/core/git/result.js.map +1 -0
  48. package/dist/src/core/http/decisions.js +225 -0
  49. package/dist/src/core/http/decisions.js.map +1 -0
  50. package/dist/{http.js → src/core/http/render.js} +472 -738
  51. package/dist/src/core/http/render.js.map +1 -0
  52. package/dist/{http-handlers.js → src/core/http/routes.js} +52 -87
  53. package/dist/src/core/http/routes.js.map +1 -0
  54. package/dist/src/core/http/views.js +181 -0
  55. package/dist/src/core/http/views.js.map +1 -0
  56. package/dist/src/core/image/managed-image.js +95 -0
  57. package/dist/src/core/image/managed-image.js.map +1 -0
  58. package/dist/src/core/issue/file.js +149 -0
  59. package/dist/src/core/issue/file.js.map +1 -0
  60. package/dist/src/core/issue/parse.js +210 -0
  61. package/dist/src/core/issue/parse.js.map +1 -0
  62. package/dist/src/core/mcp/dispatch.js +239 -0
  63. package/dist/src/core/mcp/dispatch.js.map +1 -0
  64. package/dist/src/core/mcp/post-move.js +92 -0
  65. package/dist/src/core/mcp/post-move.js.map +1 -0
  66. package/dist/src/core/mcp/protocol.js +293 -0
  67. package/dist/src/core/mcp/protocol.js.map +1 -0
  68. package/dist/src/core/mcp/url.js +162 -0
  69. package/dist/src/core/mcp/url.js.map +1 -0
  70. package/dist/src/core/path.js +63 -0
  71. package/dist/src/core/path.js.map +1 -0
  72. package/dist/src/core/reconcile/image-decide.js +48 -0
  73. package/dist/src/core/reconcile/image-decide.js.map +1 -0
  74. package/dist/src/core/reconcile/ledger.js +142 -0
  75. package/dist/src/core/reconcile/ledger.js.map +1 -0
  76. package/dist/src/core/reconcile/pr-classify.js +62 -0
  77. package/dist/src/core/reconcile/pr-classify.js.map +1 -0
  78. package/dist/{reconciler → src/core/reconcile}/pr-decide.js +25 -12
  79. package/dist/src/core/reconcile/pr-decide.js.map +1 -0
  80. package/dist/src/core/reconcile/pr-loop.js +161 -0
  81. package/dist/src/core/reconcile/pr-loop.js.map +1 -0
  82. package/dist/src/core/reconcile/pr-notes.js +35 -0
  83. package/dist/src/core/reconcile/pr-notes.js.map +1 -0
  84. package/dist/src/core/reconcile/vm-decide.js +70 -0
  85. package/dist/src/core/reconcile/vm-decide.js.map +1 -0
  86. package/dist/src/core/reconcile/vm-reap.js +207 -0
  87. package/dist/src/core/reconcile/vm-reap.js.map +1 -0
  88. package/dist/src/core/reconcile/workspace-decide.js +162 -0
  89. package/dist/src/core/reconcile/workspace-decide.js.map +1 -0
  90. package/dist/src/core/runlog/summary.js +231 -0
  91. package/dist/src/core/runlog/summary.js.map +1 -0
  92. package/dist/src/core/runner/dispatch-config.js +95 -0
  93. package/dist/src/core/runner/dispatch-config.js.map +1 -0
  94. package/dist/src/core/runner/injection.js +61 -0
  95. package/dist/src/core/runner/injection.js.map +1 -0
  96. package/dist/src/core/runner/mise.js +210 -0
  97. package/dist/src/core/runner/mise.js.map +1 -0
  98. package/dist/src/core/runner/prompt.js +720 -0
  99. package/dist/src/core/runner/prompt.js.map +1 -0
  100. package/dist/src/core/runner/turn.js +242 -0
  101. package/dist/src/core/runner/turn.js.map +1 -0
  102. package/dist/src/core/runner/vm-plan.js +390 -0
  103. package/dist/src/core/runner/vm-plan.js.map +1 -0
  104. package/dist/src/core/schedule/admission.js +123 -0
  105. package/dist/src/core/schedule/admission.js.map +1 -0
  106. package/dist/src/core/schedule/circuit-breaker.js +111 -0
  107. package/dist/src/core/schedule/circuit-breaker.js.map +1 -0
  108. package/dist/src/core/schedule/eligibility.js +83 -0
  109. package/dist/src/core/schedule/eligibility.js.map +1 -0
  110. package/dist/src/core/schedule/reconcile-issue.js +82 -0
  111. package/dist/src/core/schedule/reconcile-issue.js.map +1 -0
  112. package/dist/src/core/schedule/retry.js +96 -0
  113. package/dist/src/core/schedule/retry.js.map +1 -0
  114. package/dist/src/core/schedule/sleep-cycle.js +133 -0
  115. package/dist/src/core/schedule/sleep-cycle.js.map +1 -0
  116. package/dist/src/core/schedule/slots.js +124 -0
  117. package/dist/src/core/schedule/slots.js.map +1 -0
  118. package/dist/src/core/schedule/tick.js +553 -0
  119. package/dist/src/core/schedule/tick.js.map +1 -0
  120. package/dist/src/core/schedule/token-fold.js +181 -0
  121. package/dist/src/core/schedule/token-fold.js.map +1 -0
  122. package/dist/src/core/state-resolve.js +86 -0
  123. package/dist/src/core/state-resolve.js.map +1 -0
  124. package/dist/src/core/vm-guards.js +278 -0
  125. package/dist/src/core/vm-guards.js.map +1 -0
  126. package/dist/src/core/workflow/derive.js +107 -0
  127. package/dist/src/core/workflow/derive.js.map +1 -0
  128. package/dist/src/core/workflow/parse.js +687 -0
  129. package/dist/src/core/workflow/parse.js.map +1 -0
  130. package/dist/src/core/workflow/prompt-probe.js +78 -0
  131. package/dist/src/core/workflow/prompt-probe.js.map +1 -0
  132. package/dist/src/core/workflow/validate.js +189 -0
  133. package/dist/src/core/workflow/validate.js.map +1 -0
  134. package/dist/src/core/workspace-key.js +19 -0
  135. package/dist/src/core/workspace-key.js.map +1 -0
  136. package/dist/src/shell/actions-runner.js +356 -0
  137. package/dist/src/shell/actions-runner.js.map +1 -0
  138. package/dist/src/shell/adapter/adapter-registry.js +45 -0
  139. package/dist/src/shell/adapter/adapter-registry.js.map +1 -0
  140. package/dist/src/shell/adapter/clock-random.js +96 -0
  141. package/dist/src/shell/adapter/clock-random.js.map +1 -0
  142. package/dist/src/shell/adapter/gondolin-dispatch-helpers.js +158 -0
  143. package/dist/src/shell/adapter/gondolin-dispatch-helpers.js.map +1 -0
  144. package/dist/src/shell/adapter/gondolin-dispatch.js +385 -0
  145. package/dist/src/shell/adapter/gondolin-dispatch.js.map +1 -0
  146. package/dist/src/shell/adapter/gondolin-image-converter.js +233 -0
  147. package/dist/src/shell/adapter/gondolin-image-converter.js.map +1 -0
  148. package/dist/src/shell/adapter/gondolin-image-fetch.js +180 -0
  149. package/dist/src/shell/adapter/gondolin-image-fetch.js.map +1 -0
  150. package/dist/src/shell/adapter/launcher-asset.js +57 -0
  151. package/dist/src/shell/adapter/launcher-asset.js.map +1 -0
  152. package/dist/src/shell/adapter/mise-config-asset.js +65 -0
  153. package/dist/src/shell/adapter/mise-config-asset.js.map +1 -0
  154. package/dist/src/shell/adapter/workflow-loader.js +304 -0
  155. package/dist/src/shell/adapter/workflow-loader.js.map +1 -0
  156. package/dist/src/shell/cli/doctor.js +268 -0
  157. package/dist/src/shell/cli/doctor.js.map +1 -0
  158. package/dist/src/shell/effect-interpreter-families.js +314 -0
  159. package/dist/src/shell/effect-interpreter-families.js.map +1 -0
  160. package/dist/src/shell/effect-interpreter.js +29 -0
  161. package/dist/src/shell/effect-interpreter.js.map +1 -0
  162. package/dist/src/shell/interp/acp-frame.js +137 -0
  163. package/dist/src/shell/interp/acp-frame.js.map +1 -0
  164. package/dist/src/shell/interp/acp-ws-conn.js +320 -0
  165. package/dist/src/shell/interp/acp-ws-conn.js.map +1 -0
  166. package/dist/src/shell/interp/acp-ws-frames.js +159 -0
  167. package/dist/src/shell/interp/acp-ws-frames.js.map +1 -0
  168. package/dist/src/shell/interp/acp-ws.js +197 -0
  169. package/dist/src/shell/interp/acp-ws.js.map +1 -0
  170. package/dist/src/shell/interp/acp.js +319 -0
  171. package/dist/src/shell/interp/acp.js.map +1 -0
  172. package/dist/src/shell/interp/credential-defaults.js +128 -0
  173. package/dist/src/shell/interp/credential-defaults.js.map +1 -0
  174. package/dist/src/shell/interp/credential-hooks.js +149 -0
  175. package/dist/src/shell/interp/credential-hooks.js.map +1 -0
  176. package/dist/src/shell/interp/credential-registry.js +226 -0
  177. package/dist/src/shell/interp/credential-registry.js.map +1 -0
  178. package/dist/src/shell/interp/credential.js +103 -0
  179. package/dist/src/shell/interp/credential.js.map +1 -0
  180. package/dist/src/shell/interp/gh.js +163 -0
  181. package/dist/src/shell/interp/gh.js.map +1 -0
  182. package/dist/src/shell/interp/git.js +28 -0
  183. package/dist/src/shell/interp/git.js.map +1 -0
  184. package/dist/src/shell/interp/log.js +213 -0
  185. package/dist/src/shell/interp/log.js.map +1 -0
  186. package/dist/src/shell/interp/process.js +178 -0
  187. package/dist/src/shell/interp/process.js.map +1 -0
  188. package/dist/src/shell/interp/runlog.js +193 -0
  189. package/dist/src/shell/interp/runlog.js.map +1 -0
  190. package/dist/src/shell/interp/timer.js +64 -0
  191. package/dist/src/shell/interp/timer.js.map +1 -0
  192. package/dist/src/shell/interp/tracker-disk.js +99 -0
  193. package/dist/src/shell/interp/tracker-disk.js.map +1 -0
  194. package/dist/src/shell/interp/tracker-parse.js +71 -0
  195. package/dist/src/shell/interp/tracker-parse.js.map +1 -0
  196. package/dist/src/shell/interp/tracker-scan.js +238 -0
  197. package/dist/src/shell/interp/tracker-scan.js.map +1 -0
  198. package/dist/src/shell/interp/tracker-write.js +91 -0
  199. package/dist/src/shell/interp/tracker-write.js.map +1 -0
  200. package/dist/src/shell/interp/tracker.js +41 -0
  201. package/dist/src/shell/interp/tracker.js.map +1 -0
  202. package/dist/src/shell/interp/tty.js +48 -0
  203. package/dist/src/shell/interp/tty.js.map +1 -0
  204. package/dist/src/shell/interp/vm.js +199 -0
  205. package/dist/src/shell/interp/vm.js.map +1 -0
  206. package/dist/src/shell/interp/workspace.js +310 -0
  207. package/dist/src/shell/interp/workspace.js.map +1 -0
  208. package/dist/src/shell/main-acp.js +78 -0
  209. package/dist/src/shell/main-acp.js.map +1 -0
  210. package/dist/src/shell/main-adapters.js +222 -0
  211. package/dist/src/shell/main-adapters.js.map +1 -0
  212. package/dist/src/shell/main-credential.js +122 -0
  213. package/dist/src/shell/main-credential.js.map +1 -0
  214. package/dist/src/shell/main-doctor.js +22 -0
  215. package/dist/src/shell/main-doctor.js.map +1 -0
  216. package/dist/src/shell/main-entry.js +46 -0
  217. package/dist/src/shell/main-entry.js.map +1 -0
  218. package/dist/src/shell/main-http-csrf.js +45 -0
  219. package/dist/src/shell/main-http-csrf.js.map +1 -0
  220. package/dist/src/shell/main-http-handler.js +389 -0
  221. package/dist/src/shell/main-http-handler.js.map +1 -0
  222. package/dist/src/shell/main-http-mcp.js +122 -0
  223. package/dist/src/shell/main-http-mcp.js.map +1 -0
  224. package/dist/src/shell/main-http-views.js +253 -0
  225. package/dist/src/shell/main-http-views.js.map +1 -0
  226. package/dist/src/shell/main-http.js +76 -0
  227. package/dist/src/shell/main-http.js.map +1 -0
  228. package/dist/src/shell/main-loops.js +130 -0
  229. package/dist/src/shell/main-loops.js.map +1 -0
  230. package/dist/src/shell/main-mcp.js +129 -0
  231. package/dist/src/shell/main-mcp.js.map +1 -0
  232. package/dist/src/shell/main-orchestrator.js +120 -0
  233. package/dist/src/shell/main-orchestrator.js.map +1 -0
  234. package/dist/src/shell/main-preflight.js +43 -0
  235. package/dist/src/shell/main-preflight.js.map +1 -0
  236. package/dist/src/shell/main-reconcilers-helpers.js +244 -0
  237. package/dist/src/shell/main-reconcilers-helpers.js.map +1 -0
  238. package/dist/src/shell/main-reconcilers-pr.js +148 -0
  239. package/dist/src/shell/main-reconcilers-pr.js.map +1 -0
  240. package/dist/src/shell/main-reconcilers.js +225 -0
  241. package/dist/src/shell/main-reconcilers.js.map +1 -0
  242. package/dist/src/shell/main-runner.js +355 -0
  243. package/dist/src/shell/main-runner.js.map +1 -0
  244. package/dist/src/shell/main-scaffold.js +116 -0
  245. package/dist/src/shell/main-scaffold.js.map +1 -0
  246. package/dist/src/shell/main-shutdown.js +115 -0
  247. package/dist/src/shell/main-shutdown.js.map +1 -0
  248. package/dist/src/shell/main-startup.js +48 -0
  249. package/dist/src/shell/main-startup.js.map +1 -0
  250. package/dist/src/shell/main-substrates.js +43 -0
  251. package/dist/src/shell/main-substrates.js.map +1 -0
  252. package/dist/src/shell/main.js +385 -0
  253. package/dist/src/shell/main.js.map +1 -0
  254. package/dist/src/shell/orchestrator-feedback.js +69 -0
  255. package/dist/src/shell/orchestrator-feedback.js.map +1 -0
  256. package/dist/src/shell/orchestrator-image.js +167 -0
  257. package/dist/src/shell/orchestrator-image.js.map +1 -0
  258. package/dist/src/shell/orchestrator-loop.js +468 -0
  259. package/dist/src/shell/orchestrator-loop.js.map +1 -0
  260. package/dist/src/shell/orchestrator-reconcile.js +36 -0
  261. package/dist/src/shell/orchestrator-reconcile.js.map +1 -0
  262. package/dist/src/shell/reconciler-loop.js +228 -0
  263. package/dist/src/shell/reconciler-loop.js.map +1 -0
  264. package/dist/src/shell/runner-loop-turn.js +301 -0
  265. package/dist/src/shell/runner-loop-turn.js.map +1 -0
  266. package/dist/src/shell/runner-loop.js +338 -0
  267. package/dist/src/shell/runner-loop.js.map +1 -0
  268. package/dist/src/shell/server/http.js +208 -0
  269. package/dist/src/shell/server/http.js.map +1 -0
  270. package/dist/src/shell/server/mcp-runtime-effects.js +237 -0
  271. package/dist/src/shell/server/mcp-runtime-effects.js.map +1 -0
  272. package/dist/src/shell/server/mcp-runtime.js +99 -0
  273. package/dist/src/shell/server/mcp-runtime.js.map +1 -0
  274. package/dist/src/shell/workspace-key.js +14 -0
  275. package/dist/src/shell/workspace-key.js.map +1 -0
  276. package/dist/src/types/acp.js +8 -0
  277. package/dist/src/types/acp.js.map +1 -0
  278. package/dist/src/types/actions/plan.js +6 -0
  279. package/dist/src/types/actions/plan.js.map +1 -0
  280. package/dist/src/types/actions/predicates.js +6 -0
  281. package/dist/src/types/actions/predicates.js.map +1 -0
  282. package/dist/src/types/actions/run-fold.js +8 -0
  283. package/dist/src/types/actions/run-fold.js.map +1 -0
  284. package/dist/src/types/actions.js +7 -0
  285. package/dist/src/types/actions.js.map +1 -0
  286. package/dist/src/types/adapter/clock-random.js +4 -0
  287. package/dist/src/types/adapter/clock-random.js.map +1 -0
  288. package/dist/src/types/adapter/gondolin-image-converter.js +5 -0
  289. package/dist/src/types/adapter/gondolin-image-converter.js.map +1 -0
  290. package/dist/src/types/adapter/gondolin-image-fetch.js +5 -0
  291. package/dist/src/types/adapter/gondolin-image-fetch.js.map +1 -0
  292. package/dist/src/types/adapter/workflow-loader.js +4 -0
  293. package/dist/src/types/adapter/workflow-loader.js.map +1 -0
  294. package/dist/src/types/cli/args.js +8 -0
  295. package/dist/src/types/cli/args.js.map +1 -0
  296. package/dist/src/types/config.js +8 -0
  297. package/dist/src/types/config.js.map +1 -0
  298. package/dist/src/types/credential-interp.js +6 -0
  299. package/dist/src/types/credential-interp.js.map +1 -0
  300. package/dist/src/types/credentials.js +10 -0
  301. package/dist/src/types/credentials.js.map +1 -0
  302. package/dist/src/types/doctor.js +7 -0
  303. package/dist/src/types/doctor.js.map +1 -0
  304. package/dist/src/types/domain.js +7 -0
  305. package/dist/src/types/domain.js.map +1 -0
  306. package/dist/src/types/effect.js +15 -0
  307. package/dist/src/types/effect.js.map +1 -0
  308. package/dist/src/types/errors.js +39 -0
  309. package/dist/src/types/errors.js.map +1 -0
  310. package/dist/src/types/http/decisions.js +6 -0
  311. package/dist/src/types/http/decisions.js.map +1 -0
  312. package/dist/src/types/http/render.js +10 -0
  313. package/dist/src/types/http/render.js.map +1 -0
  314. package/dist/src/types/http/views.js +6 -0
  315. package/dist/src/types/http/views.js.map +1 -0
  316. package/dist/src/types/http.js +9 -0
  317. package/dist/src/types/http.js.map +1 -0
  318. package/dist/src/types/image/managed-image.js +7 -0
  319. package/dist/src/types/image/managed-image.js.map +1 -0
  320. package/dist/src/types/interp/effect-interpreter.js +8 -0
  321. package/dist/src/types/interp/effect-interpreter.js.map +1 -0
  322. package/dist/src/types/interp/tracker.js +7 -0
  323. package/dist/src/types/interp/tracker.js.map +1 -0
  324. package/dist/src/types/issue/file.js +6 -0
  325. package/dist/src/types/issue/file.js.map +1 -0
  326. package/dist/src/types/issue/parse.js +8 -0
  327. package/dist/src/types/issue/parse.js.map +1 -0
  328. package/dist/src/types/main-acp.js +13 -0
  329. package/dist/src/types/main-acp.js.map +1 -0
  330. package/dist/src/types/main-adapters.js +5 -0
  331. package/dist/src/types/main-adapters.js.map +1 -0
  332. package/dist/src/types/main-credential.js +21 -0
  333. package/dist/src/types/main-credential.js.map +1 -0
  334. package/dist/src/types/main-doctor.js +6 -0
  335. package/dist/src/types/main-doctor.js.map +1 -0
  336. package/dist/src/types/main-http-handler.js +12 -0
  337. package/dist/src/types/main-http-handler.js.map +1 -0
  338. package/dist/src/types/main-http.js +5 -0
  339. package/dist/src/types/main-http.js.map +1 -0
  340. package/dist/src/types/main-loops.js +5 -0
  341. package/dist/src/types/main-loops.js.map +1 -0
  342. package/dist/src/types/main-mcp.js +12 -0
  343. package/dist/src/types/main-mcp.js.map +1 -0
  344. package/dist/src/types/main-orchestrator.js +5 -0
  345. package/dist/src/types/main-orchestrator.js.map +1 -0
  346. package/dist/src/types/main-reconcilers.js +11 -0
  347. package/dist/src/types/main-reconcilers.js.map +1 -0
  348. package/dist/src/types/main-runner.js +13 -0
  349. package/dist/src/types/main-runner.js.map +1 -0
  350. package/dist/src/types/main-startup.js +5 -0
  351. package/dist/src/types/main-startup.js.map +1 -0
  352. package/dist/src/types/main-substrates.js +5 -0
  353. package/dist/src/types/main-substrates.js.map +1 -0
  354. package/dist/src/types/mcp/dispatch.js +4 -0
  355. package/dist/src/types/mcp/dispatch.js.map +1 -0
  356. package/dist/src/types/mcp/post-move.js +7 -0
  357. package/dist/src/types/mcp/post-move.js.map +1 -0
  358. package/dist/src/types/mcp.js +9 -0
  359. package/dist/src/types/mcp.js.map +1 -0
  360. package/dist/src/types/ports.js +12 -0
  361. package/dist/src/types/ports.js.map +1 -0
  362. package/dist/src/types/reconcile/image-decide.js +5 -0
  363. package/dist/src/types/reconcile/image-decide.js.map +1 -0
  364. package/dist/src/types/reconcile/ledger.js +7 -0
  365. package/dist/src/types/reconcile/ledger.js.map +1 -0
  366. package/dist/src/types/reconcile/pr-loop.js +8 -0
  367. package/dist/src/types/reconcile/pr-loop.js.map +1 -0
  368. package/dist/src/types/reconcile/vm-reap.js +8 -0
  369. package/dist/src/types/reconcile/vm-reap.js.map +1 -0
  370. package/dist/src/types/reconcile/workspace-decide.js +7 -0
  371. package/dist/src/types/reconcile/workspace-decide.js.map +1 -0
  372. package/dist/src/types/reconcile.js +9 -0
  373. package/dist/src/types/reconcile.js.map +1 -0
  374. package/dist/src/types/runlog.js +7 -0
  375. package/dist/src/types/runlog.js.map +1 -0
  376. package/dist/src/types/runner/actions-runner.js +12 -0
  377. package/dist/src/types/runner/actions-runner.js.map +1 -0
  378. package/dist/src/types/runner/gondolin-dispatch.js +5 -0
  379. package/dist/src/types/runner/gondolin-dispatch.js.map +1 -0
  380. package/dist/src/types/runner/injection.js +6 -0
  381. package/dist/src/types/runner/injection.js.map +1 -0
  382. package/dist/src/types/runner/runner-loop.js +5 -0
  383. package/dist/src/types/runner/runner-loop.js.map +1 -0
  384. package/dist/src/types/runner/turn.js +4 -0
  385. package/dist/src/types/runner/turn.js.map +1 -0
  386. package/dist/src/types/runner/vm-plan.js +4 -0
  387. package/dist/src/types/runner/vm-plan.js.map +1 -0
  388. package/dist/src/types/runtime.js +9 -0
  389. package/dist/src/types/runtime.js.map +1 -0
  390. package/dist/src/types/schedule/admission.js +7 -0
  391. package/dist/src/types/schedule/admission.js.map +1 -0
  392. package/dist/src/types/schedule/circuit-breaker.js +2 -0
  393. package/dist/src/types/schedule/circuit-breaker.js.map +1 -0
  394. package/dist/src/types/schedule/eligibility.js +9 -0
  395. package/dist/src/types/schedule/eligibility.js.map +1 -0
  396. package/dist/src/types/schedule/orchestrator-loop.js +10 -0
  397. package/dist/src/types/schedule/orchestrator-loop.js.map +1 -0
  398. package/dist/src/types/schedule/sleep-cycle.js +4 -0
  399. package/dist/src/types/schedule/sleep-cycle.js.map +1 -0
  400. package/dist/src/types/schedule/slots.js +8 -0
  401. package/dist/src/types/schedule/slots.js.map +1 -0
  402. package/dist/src/types/schedule/tick.js +9 -0
  403. package/dist/src/types/schedule/tick.js.map +1 -0
  404. package/dist/src/types/server/mcp-runtime.js +8 -0
  405. package/dist/src/types/server/mcp-runtime.js.map +1 -0
  406. package/dist/src/types/workflow/parse.js +4 -0
  407. package/dist/src/types/workflow/parse.js.map +1 -0
  408. package/dist/tests/core/account-id.test.js +35 -0
  409. package/dist/tests/core/account-id.test.js.map +1 -0
  410. package/dist/tests/core/actions-parse.test.js +176 -0
  411. package/dist/tests/core/actions-parse.test.js.map +1 -0
  412. package/dist/tests/core/adapter-config.test.js +133 -0
  413. package/dist/tests/core/adapter-config.test.js.map +1 -0
  414. package/dist/tests/core/admission.test.js +215 -0
  415. package/dist/tests/core/admission.test.js.map +1 -0
  416. package/dist/tests/core/args.test.js +132 -0
  417. package/dist/tests/core/args.test.js.map +1 -0
  418. package/dist/tests/core/availability.test.js +62 -0
  419. package/dist/tests/core/availability.test.js.map +1 -0
  420. package/dist/tests/core/checks.test.js +395 -0
  421. package/dist/tests/core/checks.test.js.map +1 -0
  422. package/dist/tests/core/circuit-breaker.test.js +172 -0
  423. package/dist/tests/core/circuit-breaker.test.js.map +1 -0
  424. package/dist/tests/core/coerce.test.js +87 -0
  425. package/dist/tests/core/coerce.test.js.map +1 -0
  426. package/dist/tests/core/context.test.js +228 -0
  427. package/dist/tests/core/context.test.js.map +1 -0
  428. package/dist/tests/core/decisions.test.js +310 -0
  429. package/dist/tests/core/decisions.test.js.map +1 -0
  430. package/dist/tests/core/derive.test.js +205 -0
  431. package/dist/tests/core/derive.test.js.map +1 -0
  432. package/dist/tests/core/dispatch-config.test.js +164 -0
  433. package/dist/tests/core/dispatch-config.test.js.map +1 -0
  434. package/dist/tests/core/dispatch.test.js +302 -0
  435. package/dist/tests/core/dispatch.test.js.map +1 -0
  436. package/dist/tests/core/eligibility.test.js +163 -0
  437. package/dist/tests/core/eligibility.test.js.map +1 -0
  438. package/dist/tests/core/extract.test.js +139 -0
  439. package/dist/tests/core/extract.test.js.map +1 -0
  440. package/dist/tests/core/fake-creds.test.js +134 -0
  441. package/dist/tests/core/fake-creds.test.js.map +1 -0
  442. package/dist/tests/core/file.test.js +197 -0
  443. package/dist/tests/core/file.test.js.map +1 -0
  444. package/dist/tests/core/git-result.test.js +113 -0
  445. package/dist/tests/core/git-result.test.js.map +1 -0
  446. package/dist/tests/core/identity.test.js +180 -0
  447. package/dist/tests/core/identity.test.js.map +1 -0
  448. package/dist/tests/core/image-decide.test.js +59 -0
  449. package/dist/tests/core/image-decide.test.js.map +1 -0
  450. package/dist/tests/core/injection.test.js +163 -0
  451. package/dist/tests/core/injection.test.js.map +1 -0
  452. package/dist/tests/core/ledger.test.js +218 -0
  453. package/dist/tests/core/ledger.test.js.map +1 -0
  454. package/dist/tests/core/managed-image.test.js +68 -0
  455. package/dist/tests/core/managed-image.test.js.map +1 -0
  456. package/dist/tests/core/mise.test.js +138 -0
  457. package/dist/tests/core/mise.test.js.map +1 -0
  458. package/dist/tests/core/parse.test.js +174 -0
  459. package/dist/tests/core/parse.test.js.map +1 -0
  460. package/dist/tests/core/path.test.js +50 -0
  461. package/dist/tests/core/path.test.js.map +1 -0
  462. package/dist/tests/core/plan.test.js +218 -0
  463. package/dist/tests/core/plan.test.js.map +1 -0
  464. package/dist/tests/core/post-move.test.js +162 -0
  465. package/dist/tests/core/post-move.test.js.map +1 -0
  466. package/dist/tests/core/pr-classify.test.js +117 -0
  467. package/dist/tests/core/pr-classify.test.js.map +1 -0
  468. package/dist/tests/core/pr-decide.test.js +298 -0
  469. package/dist/tests/core/pr-decide.test.js.map +1 -0
  470. package/dist/tests/core/pr-loop.test.js +301 -0
  471. package/dist/tests/core/pr-loop.test.js.map +1 -0
  472. package/dist/tests/core/pr-notes.test.js +165 -0
  473. package/dist/tests/core/pr-notes.test.js.map +1 -0
  474. package/dist/tests/core/predicates.test.js +154 -0
  475. package/dist/tests/core/predicates.test.js.map +1 -0
  476. package/dist/tests/core/prompt.test.js +189 -0
  477. package/dist/tests/core/prompt.test.js.map +1 -0
  478. package/dist/tests/core/protocol.test.js +195 -0
  479. package/dist/tests/core/protocol.test.js.map +1 -0
  480. package/dist/tests/core/reconcile-issue.test.js +116 -0
  481. package/dist/tests/core/reconcile-issue.test.js.map +1 -0
  482. package/dist/tests/core/render.test.js +549 -0
  483. package/dist/tests/core/render.test.js.map +1 -0
  484. package/dist/tests/core/retry.test.js +186 -0
  485. package/dist/tests/core/retry.test.js.map +1 -0
  486. package/dist/tests/core/routes.test.js +247 -0
  487. package/dist/tests/core/routes.test.js.map +1 -0
  488. package/dist/tests/core/run-fold.test.js +299 -0
  489. package/dist/tests/core/run-fold.test.js.map +1 -0
  490. package/dist/tests/core/shape.test.js +185 -0
  491. package/dist/tests/core/shape.test.js.map +1 -0
  492. package/dist/tests/core/sleep-cycle.test.js +150 -0
  493. package/dist/tests/core/sleep-cycle.test.js.map +1 -0
  494. package/dist/tests/core/slots.test.js +201 -0
  495. package/dist/tests/core/slots.test.js.map +1 -0
  496. package/dist/tests/core/state-resolve.test.js +80 -0
  497. package/dist/tests/core/state-resolve.test.js.map +1 -0
  498. package/dist/tests/core/summary.test.js +200 -0
  499. package/dist/tests/core/summary.test.js.map +1 -0
  500. package/dist/tests/core/template.test.js +116 -0
  501. package/dist/tests/core/template.test.js.map +1 -0
  502. package/dist/tests/core/tick.test.js +558 -0
  503. package/dist/tests/core/tick.test.js.map +1 -0
  504. package/dist/tests/core/token-fold.test.js +176 -0
  505. package/dist/tests/core/token-fold.test.js.map +1 -0
  506. package/dist/tests/core/turn.test.js +388 -0
  507. package/dist/tests/core/turn.test.js.map +1 -0
  508. package/dist/tests/core/url.test.js +118 -0
  509. package/dist/tests/core/url.test.js.map +1 -0
  510. package/dist/tests/core/validate.test.js +247 -0
  511. package/dist/tests/core/validate.test.js.map +1 -0
  512. package/dist/tests/core/views.test.js +252 -0
  513. package/dist/tests/core/views.test.js.map +1 -0
  514. package/dist/tests/core/vm-decide.test.js +110 -0
  515. package/dist/tests/core/vm-decide.test.js.map +1 -0
  516. package/dist/tests/core/vm-guards.test.js +153 -0
  517. package/dist/tests/core/vm-guards.test.js.map +1 -0
  518. package/dist/tests/core/vm-plan.test.js +332 -0
  519. package/dist/tests/core/vm-plan.test.js.map +1 -0
  520. package/dist/tests/core/vm-reap.test.js +196 -0
  521. package/dist/tests/core/vm-reap.test.js.map +1 -0
  522. package/dist/tests/core/workflow-parse.test.js +493 -0
  523. package/dist/tests/core/workflow-parse.test.js.map +1 -0
  524. package/dist/tests/core/workspace-decide.test.js +236 -0
  525. package/dist/tests/core/workspace-decide.test.js.map +1 -0
  526. package/dist/tests/helpers/fixtures.js +167 -0
  527. package/dist/tests/helpers/fixtures.js.map +1 -0
  528. package/dist/tests/shell/acp-substrate.test.js +101 -0
  529. package/dist/tests/shell/acp-substrate.test.js.map +1 -0
  530. package/dist/tests/shell/actions-runner-push.test.js +203 -0
  531. package/dist/tests/shell/actions-runner-push.test.js.map +1 -0
  532. package/dist/tests/shell/credential-hooks.test.js +36 -0
  533. package/dist/tests/shell/credential-hooks.test.js.map +1 -0
  534. package/dist/tests/shell/credential-registry.test.js +165 -0
  535. package/dist/tests/shell/credential-registry.test.js.map +1 -0
  536. package/dist/tests/shell/credential-substrate.test.js +179 -0
  537. package/dist/tests/shell/credential-substrate.test.js.map +1 -0
  538. package/dist/tests/shell/dockerfile-mise-pin.test.js +51 -0
  539. package/dist/tests/shell/dockerfile-mise-pin.test.js.map +1 -0
  540. package/dist/tests/shell/doctor.test.js +101 -0
  541. package/dist/tests/shell/doctor.test.js.map +1 -0
  542. package/dist/tests/shell/effect-vm-create.test.js +52 -0
  543. package/dist/tests/shell/effect-vm-create.test.js.map +1 -0
  544. package/dist/tests/shell/gh-port.test.js +63 -0
  545. package/dist/tests/shell/gh-port.test.js.map +1 -0
  546. package/dist/tests/shell/gondolin-dispatch-guard.test.js +144 -0
  547. package/dist/tests/shell/gondolin-dispatch-guard.test.js.map +1 -0
  548. package/dist/tests/shell/gondolin-dispatch-shquote.test.js +168 -0
  549. package/dist/tests/shell/gondolin-dispatch-shquote.test.js.map +1 -0
  550. package/dist/tests/shell/gondolin-image-converter.test.js +208 -0
  551. package/dist/tests/shell/gondolin-image-converter.test.js.map +1 -0
  552. package/dist/tests/shell/gondolin-image-fetch.test.js +93 -0
  553. package/dist/tests/shell/gondolin-image-fetch.test.js.map +1 -0
  554. package/dist/tests/shell/http-handler.test.js +608 -0
  555. package/dist/tests/shell/http-handler.test.js.map +1 -0
  556. package/dist/tests/shell/http-server.test.js +53 -0
  557. package/dist/tests/shell/http-server.test.js.map +1 -0
  558. package/dist/tests/shell/mcp-runtime.test.js +366 -0
  559. package/dist/tests/shell/mcp-runtime.test.js.map +1 -0
  560. package/dist/tests/shell/mise-config-asset.test.js +87 -0
  561. package/dist/tests/shell/mise-config-asset.test.js.map +1 -0
  562. package/dist/tests/shell/orchestrator-loop.test.js +583 -0
  563. package/dist/tests/shell/orchestrator-loop.test.js.map +1 -0
  564. package/dist/tests/shell/reconciler-passes.test.js +314 -0
  565. package/dist/tests/shell/reconciler-passes.test.js.map +1 -0
  566. package/dist/tests/shell/runner-loop-turn.test.js +97 -0
  567. package/dist/tests/shell/runner-loop-turn.test.js.map +1 -0
  568. package/dist/tests/shell/runner-slice.test.js +536 -0
  569. package/dist/tests/shell/runner-slice.test.js.map +1 -0
  570. package/dist/tests/shell/scaffold.test.js +65 -0
  571. package/dist/tests/shell/scaffold.test.js.map +1 -0
  572. package/dist/tests/shell/tick-config.test.js +83 -0
  573. package/dist/tests/shell/tick-config.test.js.map +1 -0
  574. package/dist/tests/shell/tracker-parse-dates.test.js +44 -0
  575. package/dist/tests/shell/tracker-parse-dates.test.js.map +1 -0
  576. package/dist/tests/shell/tracker-write-issue.test.js +154 -0
  577. package/dist/tests/shell/tracker-write-issue.test.js.map +1 -0
  578. package/dist/tests/shell/workflow-prompt-split.test.js +208 -0
  579. package/dist/tests/shell/workflow-prompt-split.test.js.map +1 -0
  580. package/dist/tests/shell/workspace-live-config.test.js +140 -0
  581. package/dist/tests/shell/workspace-live-config.test.js.map +1 -0
  582. package/package.json +21 -9
  583. package/patches/@earendil-works+gondolin+0.12.0.patch +173 -0
  584. package/prompts/Reflect.md +91 -0
  585. package/prompts/Review.md +97 -0
  586. package/prompts/Todo.md +96 -0
  587. package/prompts/_footer.md +41 -0
  588. package/prompts/_preamble.md +42 -0
  589. package/prompts-minimal/Todo.md +26 -0
  590. package/scripts/postinstall.mjs +63 -0
  591. package/scripts/vm-agent.mjs +312 -90
  592. package/WORKFLOW.md +0 -744
  593. package/dist/acp-bridge.js +0 -324
  594. package/dist/acp-bridge.js.map +0 -1
  595. package/dist/actions/cache.js +0 -191
  596. package/dist/actions/cache.js.map +0 -1
  597. package/dist/actions/effects.js +0 -41
  598. package/dist/actions/effects.js.map +0 -1
  599. package/dist/actions/executor.js +0 -570
  600. package/dist/actions/executor.js.map +0 -1
  601. package/dist/actions/index.js +0 -13
  602. package/dist/actions/index.js.map +0 -1
  603. package/dist/actions/parsing.js.map +0 -1
  604. package/dist/actions/predicate-env.js +0 -27
  605. package/dist/actions/predicate-env.js.map +0 -1
  606. package/dist/actions/predicates.js +0 -49
  607. package/dist/actions/predicates.js.map +0 -1
  608. package/dist/actions/templating.js +0 -66
  609. package/dist/actions/templating.js.map +0 -1
  610. package/dist/actions/types.js +0 -15
  611. package/dist/actions/types.js.map +0 -1
  612. package/dist/agent/acp.js +0 -473
  613. package/dist/agent/acp.js.map +0 -1
  614. package/dist/agent/adapter-names.js +0 -159
  615. package/dist/agent/adapter-names.js.map +0 -1
  616. package/dist/agent/adapters.js +0 -511
  617. package/dist/agent/adapters.js.map +0 -1
  618. package/dist/agent/credential-extractors.js +0 -342
  619. package/dist/agent/credential-extractors.js.map +0 -1
  620. package/dist/agent/credential-secrets.js +0 -628
  621. package/dist/agent/credential-secrets.js.map +0 -1
  622. package/dist/agent/credential-ticker.js +0 -57
  623. package/dist/agent/credential-ticker.js.map +0 -1
  624. package/dist/agent/gondolin-creds-staging.js +0 -356
  625. package/dist/agent/gondolin-creds-staging.js.map +0 -1
  626. package/dist/agent/gondolin-dispatch.js +0 -375
  627. package/dist/agent/gondolin-dispatch.js.map +0 -1
  628. package/dist/agent/gondolin.js +0 -124
  629. package/dist/agent/gondolin.js.map +0 -1
  630. package/dist/agent/runner-decisions.js +0 -134
  631. package/dist/agent/runner-decisions.js.map +0 -1
  632. package/dist/agent/runner.js +0 -1456
  633. package/dist/agent/runner.js.map +0 -1
  634. package/dist/agent/tool-call-summary.js +0 -102
  635. package/dist/agent/tool-call-summary.js.map +0 -1
  636. package/dist/agent/vm-acp-mapping.js +0 -73
  637. package/dist/agent/vm-acp-mapping.js.map +0 -1
  638. package/dist/agent/vm-guards.js +0 -262
  639. package/dist/agent/vm-guards.js.map +0 -1
  640. package/dist/agent/vm-port.js +0 -22
  641. package/dist/agent/vm-port.js.map +0 -1
  642. package/dist/agent/vm-process-registry.js +0 -79
  643. package/dist/agent/vm-process-registry.js.map +0 -1
  644. package/dist/bin/cli-args.js +0 -105
  645. package/dist/bin/cli-args.js.map +0 -1
  646. package/dist/bin/symphony.js +0 -794
  647. package/dist/bin/symphony.js.map +0 -1
  648. package/dist/errors.js +0 -15
  649. package/dist/errors.js.map +0 -1
  650. package/dist/http-disk.js +0 -135
  651. package/dist/http-disk.js.map +0 -1
  652. package/dist/http-handlers.js.map +0 -1
  653. package/dist/http.js.map +0 -1
  654. package/dist/issues.js +0 -178
  655. package/dist/issues.js.map +0 -1
  656. package/dist/logging.js +0 -203
  657. package/dist/logging.js.map +0 -1
  658. package/dist/mcp.js +0 -706
  659. package/dist/mcp.js.map +0 -1
  660. package/dist/memory.js +0 -85
  661. package/dist/memory.js.map +0 -1
  662. package/dist/orchestrator-decisions.js +0 -331
  663. package/dist/orchestrator-decisions.js.map +0 -1
  664. package/dist/orchestrator.js +0 -1569
  665. package/dist/orchestrator.js.map +0 -1
  666. package/dist/prompt.js +0 -65
  667. package/dist/prompt.js.map +0 -1
  668. package/dist/reconciler/cache.js +0 -65
  669. package/dist/reconciler/cache.js.map +0 -1
  670. package/dist/reconciler/index.js +0 -448
  671. package/dist/reconciler/index.js.map +0 -1
  672. package/dist/reconciler/ledger.js +0 -131
  673. package/dist/reconciler/ledger.js.map +0 -1
  674. package/dist/reconciler/pr-adapters.js +0 -174
  675. package/dist/reconciler/pr-adapters.js.map +0 -1
  676. package/dist/reconciler/pr-decide.js.map +0 -1
  677. package/dist/reconciler/pr.js +0 -422
  678. package/dist/reconciler/pr.js.map +0 -1
  679. package/dist/reconciler/types.js +0 -12
  680. package/dist/reconciler/types.js.map +0 -1
  681. package/dist/reconciler/vm.js +0 -243
  682. package/dist/reconciler/vm.js.map +0 -1
  683. package/dist/reconciler/workspace-defaults.js +0 -83
  684. package/dist/reconciler/workspace-defaults.js.map +0 -1
  685. package/dist/reconciler/workspace.js +0 -272
  686. package/dist/reconciler/workspace.js.map +0 -1
  687. package/dist/runlog.js +0 -403
  688. package/dist/runlog.js.map +0 -1
  689. package/dist/scaffold.js +0 -165
  690. package/dist/scaffold.js.map +0 -1
  691. package/dist/trackers/local.js +0 -445
  692. package/dist/trackers/local.js.map +0 -1
  693. package/dist/trackers/types.js +0 -10
  694. package/dist/trackers/types.js.map +0 -1
  695. package/dist/types.js +0 -3
  696. package/dist/types.js.map +0 -1
  697. package/dist/util/clock.js +0 -12
  698. package/dist/util/clock.js.map +0 -1
  699. package/dist/util/crypto.js +0 -25
  700. package/dist/util/crypto.js.map +0 -1
  701. package/dist/util/frontmatter.js +0 -70
  702. package/dist/util/frontmatter.js.map +0 -1
  703. package/dist/util/fs-issues.js +0 -22
  704. package/dist/util/fs-issues.js.map +0 -1
  705. package/dist/util/process.js +0 -152
  706. package/dist/util/process.js.map +0 -1
  707. package/dist/util/workspace-key.js +0 -10
  708. package/dist/util/workspace-key.js.map +0 -1
  709. package/dist/workflow-loader.js +0 -147
  710. package/dist/workflow-loader.js.map +0 -1
  711. package/dist/workflow.js +0 -822
  712. package/dist/workflow.js.map +0 -1
  713. package/dist/workspace-types.js +0 -8
  714. package/dist/workspace-types.js.map +0 -1
  715. package/dist/workspace.js +0 -443
  716. package/dist/workspace.js.map +0 -1
@@ -1,23 +1,24 @@
1
- <!--
2
- WORKFLOW.template.md — annotated reference for symphony workflow files.
3
-
4
- A workflow file is a YAML front-matter block plus a Liquid-templated prompt
5
- body. The orchestrator parses the front matter into ServiceConfig (defined in
6
- src/types.ts) and renders the prompt body once per dispatched issue, with the
7
- Liquid context `{ issue, attempt }`.
8
-
9
- This document lists every supported section, every option within it, the
10
- parser default, and a small example. For a complete worked example, see
11
- WORKFLOW.md in this repo.
12
-
13
- Notation:
14
- Required keys are marked (required).
15
- • Types: `string`, `int`, `bool`, `path` (string resolved relative to the
16
- workflow file's directory unless absolute), `string[]`, `map<K, V>`.
17
- Defaults are what the parser writes when the key is absent.
18
- -->
19
-
20
- ---
1
+ # ─────────────────────────────────────────────────────────────────────────────
2
+ # WORKFLOW.template.yaml — annotated reference for symphony workflow files.
3
+ #
4
+ # A workflow file is pure YAML config. The orchestrator parses it into
5
+ # ServiceConfig (see src/types/config.ts) and, per dispatched issue, renders the
6
+ # active state's prompt its `prompt_file` wrapped in the shared
7
+ # `prompt.preamble_file` / `prompt.footer_file` with the Liquid context
8
+ # `{ issue, attempt }`.
9
+ #
10
+ # This document lists every supported section, every option within it, the
11
+ # parser default, and a small example. For a complete worked example, see
12
+ # WORKFLOW.yaml in this repo. For the smallest thing that runs — what
13
+ # `npx smol-symphony` scaffolds by default — see WORKFLOW.minimal.yaml; reach for
14
+ # the options below only when you want a power feature it omits.
15
+ #
16
+ # Notation:
17
+ # Required keys are marked (required).
18
+ # • Types: `string`, `int`, `bool`, `path` (string resolved relative to the
19
+ # workflow file's directory unless absolute), `string[]`, `map<K, V>`.
20
+ # • Defaults are what the parser writes when the key is absent.
21
+ # ─────────────────────────────────────────────────────────────────────────────
21
22
  # ─────────────────────────────────────────────────────────────────────────────
22
23
  # tracker — where issues come from.
23
24
  # ─────────────────────────────────────────────────────────────────────────────
@@ -26,9 +27,15 @@ tracker:
26
27
  # files under `root`, one per issue, organized by state subdirectory).
27
28
  kind: local
28
29
 
29
- # root (path): directory containing `<state>/<id>.md` files. Required.
30
- # Resolved relative to the workflow file if not absolute.
31
- root: ./issues
30
+ # root (path): directory containing `<state>/<id>.md` files. Optional.
31
+ # An explicit value resolves absolute as-is, `~`-expanded against $HOME, or
32
+ # (relative) against the workflow file's directory.
33
+ # Default (unset): ~/.symphony/trackers/<project> — durable tracker state lives
34
+ # OUTSIDE the repo working tree by default, so it survives a delete / re-clone
35
+ # of the repo. <project> is the `workspace.github_repo` slug's repo name
36
+ # (owner/repo → repo), falling back to this workflow file's directory basename;
37
+ # sanitized so two different projects on one machine never collide.
38
+ # root: ~/.symphony/trackers/my-project
32
39
 
33
40
  # ─────────────────────────────────────────────────────────────────────────────
34
41
  # states — per-state configuration map. REQUIRED. Every workflow must declare
@@ -47,17 +54,21 @@ tracker:
47
54
  # and the landing directory for `symphony.propose_issue`.
48
55
  # adapter (string, optional): override the workflow-level `acp.adapter` for
49
56
  # agents dispatched in this state. Must be a known profile (claude,
50
- # codex, opencode). All use host-side credential substitution at
51
- # Gondolin egress and are startup-probed so a missing credential
52
- # fails fast. claude has a
53
- # single host credential file (~/.claude/.credentials.json) that is
54
- # probed for readability; codex passes when either ~/.codex/auth.json
55
- # holds a token (ChatGPT-OAuth tokens.access_token or a top-level
56
- # OPENAI_API_KEY) or the host OPENAI_API_KEY env var is set; opencode
57
- # passes when either ~/.local/share/opencode/auth.json holds a
58
- # github-copilot token (run `opencode auth login` -> GitHub Copilot
59
- # on the host) or a COPILOT_GITHUB_TOKEN/GH_TOKEN/GITHUB_TOKEN env
60
- # var is set.
57
+ # codex). Both use host-side credential substitution at Gondolin
58
+ # egress and are startup-probed so a missing credential fails fast.
59
+ # claude has a single host credential file
60
+ # (~/.claude/.credentials.json) that is probed for readability; codex
61
+ # passes when either ~/.codex/auth.json holds a token (ChatGPT-OAuth
62
+ # tokens.access_token or a top-level OPENAI_API_KEY) or the host
63
+ # OPENAI_API_KEY env var is set.
64
+ # prompt_file (path): this state's prompt template, in its own file. The shell
65
+ # loader reads it and assembles `prompt.preamble_file` + this file +
66
+ # `prompt.footer_file` (see the top-level `prompt:` block below) into
67
+ # the template rendered for issues in this state. Every active
68
+ # (dispatched) state should declare one; a state without `prompt_file`
69
+ # renders the generic fallback prompt. Resolved relative to the
70
+ # workflow file. A missing/unparseable file fails at startup (and
71
+ # `symphony doctor`), not mid-dispatch.
61
72
  # model (string, optional): override `acp.model` for this state.
62
73
  # Blank or whitespace-only values normalize to "use the adapter
63
74
  # default" (same as the workflow-level acp.model semantics).
@@ -117,12 +128,7 @@ tracker:
117
128
  # Recognized kinds:
118
129
  # push_branch { remote, ref }
119
130
  # create_pr_if_missing { base, head, title_from, body_from }
120
- # ensure_branch { name, seed_from? }
121
- # checkout { ref }
122
- # merge { source, target, on_conflict }
123
- # delete_branch { name, scope: local|remote|both, remote? }
124
131
  # run_in_vm { name, cmd: [...], env?, timeout? }
125
- # propose_followup { title, body?, labels?, priority? }
126
132
  #
127
133
  # Templating: `$varname` references the fixed ActionContext
128
134
  # namespace ($identifier, $workspace, $branch, $base_branch,
@@ -137,20 +143,24 @@ tracker:
137
143
  # Retry: optional `on_error.retry: { count, backoff_ms }`. Default
138
144
  # policy is 3 retries with exponential backoff starting at 1s,
139
145
  # then abort. `on_error.then: { route_to: <state> }` reroutes the
140
- # issue to a holding state instead of aborting.
141
- #
142
- # merge's `on_conflict: { route_to: <state> }` is a fast-path
143
- # reroute. Use `on_conflict: abort` to fail the action and abort
144
- # the cleanup pass without a state move.
145
- #
146
- # run_in_vm has content-hash caching: identical (workspace tree
147
- # cmd env) tuples skip execution and re-use the prior
148
- # successful result. The workspace-tree hash reflects live
149
- # contents (tracked + modified + untracked-not-gitignored), so
150
- # an uncommitted agent edit forces a cache miss. Cache lives
151
- # under `~/.cache/symphony/actions/run_in_vm/<name>/<sha256>/`.
152
- # `symphony rerun --check=<name>` drops the whole `<name>/`
153
- # namespace dir so the next dispatch re-runs that one check.
146
+ # issue OUT of the action's state into <state> (which must be
147
+ # declared — validated at parse time) instead of aborting; the
148
+ # runner moves the tracker file there with the failure reason
149
+ # appended as notes. The canonical use is the Done handoff (issue
150
+ # 235): a `push_branch` / `create_pr_if_missing` that fails after
151
+ # its retries reroutes into a `HandoffFailed` holding state so a
152
+ # stranded handoff (no branch on the remote, no PR) is operator-
153
+ # visible instead of dead-ending silently in terminal Done. A
154
+ # rerouted handoff also RETAINS the per-issue workspace (the
155
+ # handoff-pending marker, issue 234) so the unpushed commit
156
+ # survives for a manual push. A `.github/workflows/`-touching push
157
+ # is the common trigger: the shared fine-grained PAT is not granted
158
+ # Workflows:write, so GitHub server-side-declines it and the routed
159
+ # reason is rewritten to an actionable "manual SSH push required".
160
+ #
161
+ # run_in_vm runs uncached: every dispatch into the state re-execs
162
+ # the command on the live per-issue VM. `name` labels its run-log
163
+ # output (no caching layer; the action is the plain escape hatch).
154
164
  # pr (map, optional): PR autopilot routing for this state (issue 139).
155
165
  # Valid only on a `terminal` state, and only acts when the
156
166
  # top-level `pr:` engine block (below) has `enabled: true`. This is
@@ -174,6 +184,39 @@ tracker:
174
184
  # terminal state may declare `auto_merge`, and at most one may
175
185
  # declare `close`; an `on_conflict.route_to` naming an undeclared
176
186
  # state is rejected at parse time.
187
+ # spawn (map, optional): recurring-spawn trigger for this state (the sleep
188
+ # cycle as a recurring STATE). Valid only on an `active` state. When
189
+ # a trigger fires the orchestrator MINTS a FRESH ephemeral issue into
190
+ # this state (it runs once and terminates in a dedicated terminal),
191
+ # instead of re-arming one immortal parked ticket. Fields:
192
+ # • on_idle (bool): spawn when the orchestrator is idle AND >=1
193
+ # issue reached a terminal state since the last cycle (the ">=1
194
+ # since last cycle" gate is load-bearing — without it an idle
195
+ # orchestrator re-spawns in a tight loop with nothing new to
196
+ # mine). Default false.
197
+ # • after_terminal (int): a backstop for busy stretches that never
198
+ # go idle — spawn once this many issues have reached a terminal
199
+ # state (Done/Cancelled/the spawn's terminal) since the last
200
+ # cycle. 0 disables the count trigger. Default 0.
201
+ # • title (string): title template for the minted issue;
202
+ # `{{ stamp }}` → the mint timestamp (e.g. "2026-06-03 12:00").
203
+ # Default "Reflection {{ stamp }}".
204
+ # • body (string, optional): lead-in line for the GENERATED
205
+ # provenance body (the trigger + counter + timestamp the static
206
+ # prompt can't know are always appended). Default: a generic
207
+ # "Auto-spawned reflection cycle." lead-in.
208
+ # • max_in_flight (int): never mint a second issue while this many
209
+ # are already live in the spawn state. Load-bearing — it replaces
210
+ # the immortal ticket's built-in "one location = one reflection"
211
+ # mutex, so a busy-stretch after_terminal trigger can't pile up
212
+ # cycles. Default 1.
213
+ # The terminal-transition counter resets to 0 on a successful mint
214
+ # and is held in orchestrator memory only (a restart resets it). At
215
+ # most one active state may declare `spawn` (so the spawn state is
216
+ # unambiguous). The spawn's terminal must be a DEDICATED terminal
217
+ # with no `pr:` / `actions:` — a minted issue runs on an empty branch,
218
+ # so a Done-style PR autopilot would try to open a PR off nothing.
219
+ # See the SLEEP CYCLE section below for the full pattern.
177
220
  #
178
221
  # Declaration order matters: role-filtered listings (active states, terminal
179
222
  # states) follow it, and the dashboard renders state columns in the same order.
@@ -181,6 +224,7 @@ tracker:
181
224
  states:
182
225
  Todo:
183
226
  role: active
227
+ prompt_file: prompts/Todo.md # this state's prompt template (see `prompt:` below)
184
228
  adapter: claude
185
229
  model: claude-opus-4-7
186
230
  effort: xhigh
@@ -188,6 +232,7 @@ states:
188
232
  max_concurrent: 1 # at most one implementer agent at a time
189
233
  Review:
190
234
  role: active
235
+ prompt_file: prompts/Review.md
191
236
  adapter: codex
192
237
  model: gpt-5-codex
193
238
  max_turns: 4
@@ -205,14 +250,22 @@ states:
205
250
  # open a PR if one does not already exist. Templates resolve against the
206
251
  # fixed ActionContext namespace; the orchestrator stages $pr_title and
207
252
  # $pr_body_file from the issue file before firing.
253
+ # A handoff that fails after its retries reroutes the issue OUT of Done into
254
+ # the HandoffFailed holding state (issue 235) via `on_error.then.route_to`,
255
+ # so a stranded handoff is operator-visible instead of silently terminal-Done.
208
256
  # actions:
209
- # - { kind: push_branch, remote: origin, ref: $branch, if: $repo }
257
+ # - kind: push_branch
258
+ # remote: origin
259
+ # ref: $branch
260
+ # if: $repo
261
+ # on_error: { then: { route_to: HandoffFailed } }
210
262
  # - kind: create_pr_if_missing
211
263
  # base: $base_branch
212
264
  # head: $branch
213
265
  # title_from: $pr_title
214
266
  # body_from: $pr_body_file
215
267
  # if: $repo
268
+ # on_error: { then: { route_to: HandoffFailed } }
216
269
  Cancelled:
217
270
  role: terminal
218
271
  # PR autopilot close state (issue 139). Derived as the close state because
@@ -221,11 +274,46 @@ states:
221
274
  # close: true
222
275
  Triage:
223
276
  role: holding
277
+ HandoffFailed:
278
+ # Landing state for a Done handoff that FAILED (issue 235): the failing
279
+ # action's `on_error.then.route_to` (above) reroutes the issue here with the
280
+ # reason appended. Holding → never dispatched; it parks for an operator to
281
+ # push the branch by hand (the workspace is retained by issue 234's marker)
282
+ # and move the issue back to Done, or to Cancelled. Declared AFTER Triage so
283
+ # Triage stays the `propose_issue` landing (the first declared holding state).
284
+ role: holding
224
285
 
225
286
  # ─────────────────────────────────────────────────────────────────────────────
226
- # SLEEP CYCLE a reflection state that mines finished work for harness
287
+ # promptshared prompt-assembly files (per-state prompt split). OPTIONAL.
288
+ #
289
+ # When an active state declares a `prompt_file` (see the states block above), the
290
+ # shell loader builds that state's prompt by concatenating, in order:
291
+ #
292
+ # prompt.preamble_file (shared header)
293
+ # + the state's prompt_file (the state-specific body)
294
+ # + prompt.footer_file (shared footer)
295
+ #
296
+ # and renders the result — so the config reads as the shape of the workflow and
297
+ # each state's prompt lives in its own file under `prompts/`. The Liquid context
298
+ # is `{ issue, attempt }`; each file is an ordinary Liquid template fragment.
299
+ #
300
+ # Both keys are paths resolved relative to the workflow file, and both are
301
+ # optional: omit `preamble_file`/`footer_file` to wrap a state body in nothing.
302
+ # A workflow file is pure YAML config — there is no inline prompt body. Every
303
+ # active state names its own `prompt_file`; a state without one renders the
304
+ # generic fallback prompt, so give each dispatched state a `prompt_file`. See
305
+ # WORKFLOW.minimal.yaml for the smallest example and WORKFLOW.yaml in this repo
306
+ # for a full worked example.
307
+ #
308
+ # prompt:
309
+ # preamble_file: prompts/_preamble.md
310
+ # footer_file: prompts/_footer.md
311
+ # ─────────────────────────────────────────────────────────────────────────────
312
+
313
+ # ─────────────────────────────────────────────────────────────────────────────
314
+ # SLEEP CYCLE — a recurring reflection STATE that mines finished work for harness
227
315
  # improvements (issue 122). Optional, opt-in pattern; layered on top of the
228
- # states block above. The shipped smol-symphony WORKFLOW.md wires it for the
316
+ # states block above. The shipped smol-symphony WORKFLOW.yaml wires it for the
229
317
  # dogfooding (symphony-on-symphony) setup.
230
318
  #
231
319
  # The idea: every dispatch starts from the same static prompt + config, no
@@ -233,16 +321,21 @@ states:
233
321
  # rejected, burn their turn budget, or fight the harness. A periodic
234
322
  # "reflection" turn closes that feedback loop — it reads completed-task history
235
323
  # (the read-only mounts `eval_mode` exposes), distils *recurring* friction, and
236
- # files improvement proposals against the HARNESS (this WORKFLOW.md's prompt
324
+ # files improvement proposals against the HARNESS (this WORKFLOW.yaml's prompt
237
325
  # branches and per-state model/max_turns/allowed_transitions/effort/actions, the
238
326
  # gondolin image config, acceptance criteria, timeouts) — never the product code under
239
327
  # review. Proposals land in Triage via `propose_issue`, so a human stays the
240
328
  # gate. This is the "self-improving agent" pattern aimed at the harness rather
241
329
  # than the product.
242
330
  #
243
- # Two states implement it:
331
+ # SHAPE: recurring STATE, NOT an immortal ticket. The cycle is modelled as a
332
+ # recurring state that SPAWNS ephemeral reflection issues. There is no parked
333
+ # "Sleep cycle" ticket that ping-pongs between two states forever: when a trigger
334
+ # fires the orchestrator MINTS a fresh issue into the spawn state; it runs once
335
+ # and terminates in a dedicated terminal, like every other ticket. Two states
336
+ # implement it:
244
337
  #
245
- # Reflect (role: active, eval_mode: true):
338
+ # Reflect (role: active, eval_mode: true, spawn: {...}):
246
339
  # - eval_mode binds /symphony/issues (all state dirs, incl. the Done/*.md
247
340
  # handoff transcripts) + /symphony/logs (per-issue JSONL run logs)
248
341
  # read-only into the VM. No extra mount plumbing — it reuses the existing
@@ -250,23 +343,24 @@ states:
250
343
  # - Give it a capable adapter/model (large context helps: a reflection turn
251
344
  # reads many transcripts + logs) and a higher max_turns than your
252
345
  # implement/review states.
253
- # - allowed_transitions: [Dormant] — the reflector may ONLY go dormant. It
254
- # cannot route itself into the implement/review/done flow. Filing
255
- # improvements goes through propose_issue (→ Triage), which is independent
256
- # of allowed_transitions.
257
- # - The prompt body's `when "Reflect"` branch encodes the
346
+ # - allowed_transitions: [Reflected] — the reflector may ONLY terminate in
347
+ # its dedicated terminal. It cannot route itself into the
348
+ # implement/review/done flow. Filing improvements goes through
349
+ # propose_issue (→ Triage), which is independent of allowed_transitions.
350
+ # - The `spawn:` block (see the states-field docs above) declares the
351
+ # triggers (on_idle / after_terminal), the minted-issue title template, and
352
+ # max_in_flight. This is what makes the cycle recurring — no cron needed.
353
+ # - The Reflect state's `prompt_file` encodes the
258
354
  # read → distil → propose loop and the GUARDRAILS below.
259
355
  #
260
- # Dormant (role: holding):
261
- # - Resting place for the single recurring "Sleep cycle" issue between runs.
262
- # Holding never dispatched. Declare it AFTER your Triage state so Triage
263
- # stays the first holding state (the `propose_issue` landing + triage
264
- # approve/discard target both resolve the FIRST declared holding state).
265
- # - Dashboard caveat: the dashboard currently renders triage approve/discard
266
- # buttons on every holding row, and the tracker resolves a move by issue
267
- # id regardless of source directory — so clicking those buttons on a
268
- # Dormant issue would mis-route it. Re-arm via cron/CLI/filesystem, not the
269
- # dashboard buttons.
356
+ # Reflected (role: terminal):
357
+ # - DEDICATED terminal for a completed reflection NOT Done. Done carries the
358
+ # PR autopilot `actions:` (push_branch + create_pr_if_missing); a minted
359
+ # reflection runs on an empty branch (it only files propose_issue), so
360
+ # terminating in Done would try to open a PR off nothing. So `Reflected`
361
+ # declares NO `pr:` and NO `actions:`. Each cycle lands here as a browsable,
362
+ # per-run audit trail one issue per cycle instead of one ever-growing
363
+ # immortal ticket body.
270
364
  #
271
365
  # GUARDRAILS (this is a self-modifying loop — keep the human in it):
272
366
  # - Output is proposals into Triage (holding, never auto-dispatched). The
@@ -276,13 +370,14 @@ states:
276
370
  # - Each proposal must cite the issue ids that motivated it, so the operator
277
371
  # checks the lesson against the evidence rather than the reflector's summary.
278
372
  #
279
- # CADENCE (v1 operator/scheduled-triggered, no orchestrator trigger logic):
280
- # A single recurring issue (e.g. titled "Sleep cycle") oscillates Reflect ↔
281
- # Dormant. The operator drops it into Reflect (dashboard, or `mv` on disk), or
282
- # an external cron / a `symphony reflect` verb arms it. After it files
283
- # proposals it transitions to Dormant and waits to be re-armed. Auto-arm on
284
- # idle (no active issues) or after N transitions into Done is a deliberate
285
- # follow-up, out of scope for v1.
373
+ # CADENCE: the orchestrator auto-spawns a fresh reflection issue from the
374
+ # Reflect state's `spawn:` block — `on_idle` (idle with >=1 issue finished since
375
+ # the last cycle) or `after_terminal` (a backstop once N issues have reached a
376
+ # terminal state since the last cycle). The counter resets on a successful mint;
377
+ # `max_in_flight: 1` prevents a second cycle while one is live. An operator can
378
+ # also mint a cycle by hand by creating a Reflect issue (a real dashboard
379
+ # "reflect now" button or a `symphony reflect` verb is a natural fit) — a clean
380
+ # replacement for the old "re-arm by mv/cron" path.
286
381
  #
287
382
  # Example states to add (names are yours to choose):
288
383
  #
@@ -294,11 +389,16 @@ states:
294
389
  # model: claude-opus-4-8[1m] # large context for reading transcripts
295
390
  # max_turns: 20 # higher than implement/review
296
391
  # eval_mode: true
297
- # allowed_transitions: [Dormant]
392
+ # allowed_transitions: [Reflected]
393
+ # spawn:
394
+ # on_idle: true
395
+ # after_terminal: 10 # backstop for busy stretches (0 disables)
396
+ # title: "Reflection {{ stamp }}"
397
+ # max_in_flight: 1 # never two reflections live at once
298
398
  # Triage:
299
- # role: holding # declared before Dormant
300
- # Dormant:
301
399
  # role: holding
400
+ # Reflected:
401
+ # role: terminal # dedicated terminal — NO pr:/actions:
302
402
  # ─────────────────────────────────────────────────────────────────────────────
303
403
 
304
404
  # ─────────────────────────────────────────────────────────────────────────────
@@ -359,47 +459,50 @@ pr:
359
459
  # within the window. Default 30000.
360
460
  poll_interval_ms: 30000
361
461
  # ----------------------------------------------------------------------------
362
- # SLEEP CYCLE (auto-arm) — issue 140 moved this trigger ONTO the active state it
363
- # arms. There is NO top-level `sleep_cycle:` block anymore; declare an `arm:`
364
- # block on the active state (e.g. Reflect) in the `states:` map above:
462
+ # SLEEP CYCLE (recurring spawn) — the recurrence lives in the `spawn:` block on
463
+ # the active reflection state (see the states-field docs + the SLEEP CYCLE
464
+ # section above). There is NO top-level `sleep_cycle:` block and NO `arm:` block;
465
+ # instead of re-arming one immortal parked ticket, the orchestrator MINTS a fresh
466
+ # ephemeral reflection issue when a trigger fires:
365
467
  #
366
468
  # states:
367
469
  # Reflect:
368
470
  # role: active
369
- # arm:
370
- # issue: sleep-cycle # the recurring issue to auto-enter this state
371
- # from: Dormant # the holding state it rests in between runs
372
- # on_idle: true # arm when idle with >=1 terminal since last run
373
- # after_terminal: 10 # arm after N terminal transitions (0 disables)
374
- #
375
- # SEMANTICS (unchanged from the old sleep_cycle block):
376
- # - on_idle: arm when the orchestrator is idle (nothing running, claimed, or
471
+ # allowed_transitions: [Reflected]
472
+ # spawn:
473
+ # on_idle: true # spawn when idle with >=1 terminal since last cycle
474
+ # after_terminal: 10 # spawn after N terminal transitions (0 disables)
475
+ # title: "Reflection {{ stamp }}"
476
+ # max_in_flight: 1 # never two reflections live at once
477
+ # Reflected:
478
+ # role: terminal # dedicated terminal NO pr:/actions:
479
+ #
480
+ # SEMANTICS:
481
+ # - on_idle: spawn when the orchestrator is idle (nothing running, claimed, or
377
482
  # pending retry, and no active candidate this poll) AND >=1 issue has reached
378
- # a terminal state since the last run. The ">=1 since last run" gate is
379
- # load-bearing: without it an idle orchestrator re-arms in a tight loop with
483
+ # a terminal state since the last cycle. The ">=1 since last cycle" gate is
484
+ # load-bearing: without it an idle orchestrator re-spawns in a tight loop with
380
485
  # nothing new to mine.
381
- # - after_terminal: a backstop for busy stretches that never go idle — arm once
382
- # this many issues have reached a terminal state (Done/Cancelled) since the
383
- # last run. 0 disables the count trigger.
384
- # The terminal-transition counter resets to 0 the moment the issue is armed and
385
- # is held in orchestrator memory only (a restart resets it).
486
+ # - after_terminal: a backstop for busy stretches that never go idle — spawn
487
+ # once this many issues have reached a terminal state since the last cycle.
488
+ # 0 disables the count trigger.
489
+ # - max_in_flight: never mint a second issue while this many are already live in
490
+ # the spawn state (replaces the immortal ticket's built-in one-at-a-time
491
+ # mutex). Default 1.
492
+ # The terminal-transition counter resets to 0 on a successful mint (and is
493
+ # restored if the mint fails) and is held in orchestrator memory only (a restart
494
+ # resets it). A finished reflection's OWN terminal move (out of the spawn state)
495
+ # is excluded from the counter, so a completed cycle never counts toward spawning
496
+ # the next.
386
497
  #
387
498
  # VALIDATION (structural, from each state's own role — no dedicated re-validator):
388
- # `arm:` is only valid on an `active` state, `arm.from` must be a declared
389
- # `holding` state, `arm.issue` is required, and at most one active state may
390
- # declare `arm`.
499
+ # `spawn:` is only valid on an `active` state, and at most one active state may
500
+ # declare `spawn`. Unlike the retired `arm:` block it needs no parked issue and
501
+ # no from-state.
391
502
  #
392
- # GUARDRAILS: arming ONLY moves the issue into the armed state. The proposals the
503
+ # GUARDRAILS: spawning ONLY mints the reflection issue. The proposals the
393
504
  # reflector files still land in Triage and still require human approve/discard —
394
- # arming does not bypass the human gate. Requires a single issue with id
395
- # `arm.issue` resting in `arm.from` (created by the operator); the trigger is
396
- # inert until that issue exists.
397
- #
398
- # MIGRATION: a deprecated top-level `sleep_cycle:` block is still parsed for one
399
- # release and folded onto states.<reflect_state>.arm (dormant_state -> from,
400
- # reflect_state -> the armed state, issue_id -> issue, arm_on_idle -> on_idle,
401
- # arm_after_done -> after_terminal) with a startup deprecation warning. Prefer
402
- # the per-state `arm:` block.
505
+ # spawning does not bypass the human gate.
403
506
  # ----------------------------------------------------------------------------
404
507
 
405
508
  # ─────────────────────────────────────────────────────────────────────────────
@@ -415,8 +518,11 @@ polling:
415
518
  # ─────────────────────────────────────────────────────────────────────────────
416
519
  workspace:
417
520
  # root (path): parent directory holding `<issue-id>/` working trees.
418
- # Default: $TMPDIR/symphony_workspaces
419
- root: ./.symphony/workspaces
521
+ # An explicit value resolves absolute as-is, `~`-expanded against $HOME, or
522
+ # (relative) against the workflow file's directory.
523
+ # Default (unset): ~/.symphony/workspaces/<project> (same <project> derivation
524
+ # as tracker.root above).
525
+ # root: ~/.symphony/workspaces/my-project
420
526
 
421
527
  # github_repo (string | null): the GitHub `owner/repo` slug symphony pushes the
422
528
  # per-issue `agent/<id>` branch to and opens a PR against on the Done-state
@@ -512,8 +618,11 @@ workspace:
512
618
  #
513
619
  # Console routing: while the file sink is active (the default), the structured
514
620
  # stream goes to the file ONLY — the console shows just the startup banner
515
- # (workflow, tracker root, dashboard URL, log-file path). `tail -f` the log
516
- # file to follow the detail. Pass `--verbose` (alias `--foreground`) to mirror
621
+ # (workflow, tracker root, dashboard URL, MCP endpoint, ACP `/acp` WebSocket
622
+ # endpoint, log-file path). The dashboard/MCP/ACP all share the one HTTP port,
623
+ # which defaults to ephemeral, so the banner is how the operator learns which
624
+ # port got bound. `tail -f` the
625
+ # log file to follow the detail. Pass `--verbose` (alias `--foreground`) to mirror
517
626
  # the structured stream back onto the console for interactive debugging. With
518
627
  # no file sink configured, the structured stream stays on stderr.
519
628
  #
@@ -523,8 +632,11 @@ workspace:
523
632
  # ─────────────────────────────────────────────────────────────────────────────
524
633
  logs:
525
634
  # root (path): directory holding per-issue JSONL files and symphony.log.
526
- # Default: ./.symphony/logs
527
- root: ./.symphony/logs
635
+ # An explicit value resolves absolute as-is, `~`-expanded against $HOME, or
636
+ # (relative) against the workflow file's directory.
637
+ # Default (unset): ~/.symphony/logs/<project> (same <project> derivation as
638
+ # tracker.root above) — durable run logs live OUTSIDE the repo tree by default.
639
+ # root: ~/.symphony/logs/my-project
528
640
 
529
641
  # ─────────────────────────────────────────────────────────────────────────────
530
642
  # workspace lifecycle — no shell `hooks:` surface.
@@ -537,7 +649,7 @@ logs:
537
649
  # branch cut + origin/identity) is owned by the orchestrator's TypeScript
538
650
  # `setupWorkspaceDir`. The workspace arrives at the agent with: a hardlinked
539
651
  # `git clone --local --no-tags` of the source repo (`SYMPHONY_SOURCE_REPO`,
540
- # default: the dir containing WORKFLOW.md) on the base branch
652
+ # default: the dir containing WORKFLOW.yaml) on the base branch
541
653
  # (`SYMPHONY_BASE_BRANCH`, default `main`); all network remotes stripped; an
542
654
  # `origin` restored to the canonical HTTPS URL when `workspace.github_repo`
543
655
  # (or the `SYMPHONY_REPO` env override) is set (so the Done-state push can
@@ -622,12 +734,6 @@ acp:
622
734
  # bearer (in a fake ~/.codex/auth.json); the host substitutes the
623
735
  # real OpenAI/ChatGPT token at egress. No real credential — and no
624
736
  # real OPENAI_API_KEY — enters the VM.
625
- # opencode — opencode acp, backed by GitHub Copilot (issue 130). The host
626
- # exchanges the operator's `opencode auth login` GitHub OAuth token
627
- # for a short-lived Copilot token host-side and substitutes it at
628
- # egress — the GitHub token never enters the VM. One Copilot
629
- # credential unlocks many models (GPT-4o/4.1, Claude Sonnet,
630
- # Gemini, o-series, …).
631
737
  adapter: claude
632
738
 
633
739
  # Credentials never enter the VM (issue 113; codex generalized in 116). The
@@ -650,17 +756,6 @@ acp:
650
756
  # so codex-acp runs in its native mode without an in-VM OAuth handshake or
651
757
  # refresh (both stay host-side). Every credential-bearing var is stripped from
652
758
  # the forwarded VM boot env.
653
- #
654
- # For opencode: a staged opencode.json (at /root/.config/opencode/opencode.json)
655
- # declares a custom @ai-sdk/openai-compatible provider whose baseURL/apiKey read
656
- # the OPENCODE_PROXY_* env vars (a `gho_`-shaped placeholder bearer). The host
657
- # reads the durable GitHub OAuth token from ~/.local/share/opencode/auth.json
658
- # (COPILOT_GITHUB_TOKEN/GH_TOKEN/GITHUB_TOKEN env fallback), exchanges it
659
- # host-side at api.github.com/copilot_internal/v2/token for a short-lived Copilot
660
- # token (cached + TTL-refreshed before expiry), injects the Copilot editor
661
- # headers, and substitutes it at egress to api.githubcopilot.com. The durable
662
- # GitHub token never enters the VM — so do NOT also list it in
663
- # `gondolin.forward_env`. See docs/research/opencode-copilot-accept-matrix.md.
664
759
 
665
760
  # model (string | null): optional model selector forwarded to the chosen adapter.
666
761
  # Each adapter profile knows how to surface it natively:
@@ -668,10 +763,6 @@ acp:
668
763
  # claude-agent-acp would (aliases like "opus", "sonnet", or full IDs
669
764
  # like "claude-opus-4-7").
670
765
  # codex — passed as `-c model="<value>"` argv to codex-acp (parsed as TOML).
671
- # opencode— baked into the staged opencode.json as model="symphony-copilot/<value>".
672
- # Use a Copilot chat-completions model id (e.g. gpt-4o, gpt-4.1,
673
- # claude-sonnet-4.5, gemini-2.5-pro); codex-class models served only
674
- # on Copilot's /responses path are NOT reachable. Default: gpt-4o.
675
766
  # Leave unset / null to use the adapter's own default model. Default: null.
676
767
  # model: claude-opus-4-7
677
768
 
@@ -689,7 +780,7 @@ acp:
689
780
  # Leave unset / null for the adapter's own default. Default: null.
690
781
  # effort: xhigh
691
782
 
692
- # NOTE: the launch shape is fixed (an in-VM agent dials back over the bridge
783
+ # NOTE: the launch shape is fixed (an in-VM agent dials back the `/acp` WebSocket
693
784
  # and spawns the chosen adapter). Customizing what the agent spawns requires
694
785
  # forking that agent and rebuilding the VM image with the fork in place.
695
786
 
@@ -709,37 +800,19 @@ acp:
709
800
  # the turn is killed and retried. Default: 300000
710
801
  stall_timeout_ms: 300000
711
802
 
712
- # bridgehost-side TCP listener the in-VM agent dials back to for ACP traffic.
713
- #
714
- # This replaced the earlier in-VM-exec stdio path. Symphony writes ACP JSON-RPC frames
715
- # onto an authenticated TCP socket; the in-VM agent (`/opt/symphony/vm-agent.mjs`)
716
- # spawns the adapter via `child_process.spawn` with kernel pipes and bridges the
717
- # socket to the adapter's stdio. This decouples symphony from any particular
718
- # sandbox's stdio quirks any sandbox that can launch a process with env vars and
719
- # reach the host loopback works unchanged.
720
- bridge:
721
- # bind_host (string): host symphony binds the listener on. 0.0.0.0 allows any
722
- # in-VM interface to reach the host loopback (Gondolin maps a synthetic guest host to
723
- # host loopback transparently). Default: 0.0.0.0
724
- bind_host: 0.0.0.0
725
-
726
- # bind_port (int): port symphony binds the listener on. 0 picks an ephemeral
727
- # port (used port surfaces via the in-VM SYMPHONY_ACP_URL env var). Default: 8788
728
- bind_port: 8788
729
-
730
- # reach_host (string): host the in-VM agent dials back to. Under Gondolin this is
731
- # 127.0.0.1 because the guest loopback hits the host loopback. Other sandboxes
732
- # may need a different alias. Default: 127.0.0.1
733
- reach_host: 127.0.0.1
734
-
735
- # reach_url (string|null): full URL override for the in-VM agent's dial
736
- # destination, e.g. through a reverse proxy or different scheme. When null,
737
- # symphony constructs `tcp://<reach_host>:<bind_port>`. Default: null
738
- # reach_url: null
739
-
740
- # connect_timeout_ms (int): how long to wait for the in-VM agent to connect after
741
- # the sandbox is launched, before failing the attempt. Default: 30000
742
- connect_timeout_ms: 30000
803
+ # ACP transport — the in-VM agent dials back a `/acp` WebSocket on symphony's unified
804
+ # HTTP server (the SAME listener that serves the dashboard + MCP), reached through the
805
+ # SAME `tcp.hosts` tunnel MCP uses (the guest dials `ws://symphony-mcp:7001/acp`). It
806
+ # authenticates with a per-dispatch bearer sent as the first WebSocket message, then ACP
807
+ # JSON-RPC frames flow as WebSocket frames; the in-VM agent (`/opt/symphony/vm-agent.mjs`)
808
+ # spawns the adapter via `child_process.spawn` with kernel pipes and pumps the frames to
809
+ # the adapter's stdio. There is NO separate ACP listener / bind port the raw-TCP bridge
810
+ # (`acp.bridge.*`: bind_host/bind_port/reach_host/reach_url) was retired; those keys are
811
+ # ignored if present.
812
+
813
+ # connect_timeout_ms (int): how long to wait for the in-VM agent to dial back the `/acp`
814
+ # WebSocket after the sandbox is launched, before failing the attempt. Default: 30000
815
+ connect_timeout_ms: 30000
743
816
 
744
817
  # ─────────────────────────────────────────────────────────────────────────────
745
818
  # credentials — host credential lifecycle (issue 113). The host substitutes the
@@ -758,20 +831,82 @@ credentials:
758
831
  # systemd timer instead). Default: 21600000 (6 hours).
759
832
  ticker_interval_ms: 21600000
760
833
 
834
+ # refresh_margin_ms (int): how far ahead of a token's expiry the host refreshes it.
835
+ # Drives BOTH the per-VM proactive tick (scheduled at `expiresAt - margin`) AND the
836
+ # egress request-side lazy refresh (a request bound for a credential host within this
837
+ # margin of expiry awaits a fresh token before going out). Must comfortably cover the
838
+ # refresh round-trip (`claude -p ok` rotate + re-read + fan-out) plus any in-flight long
839
+ # request, or a token expiring mid-dispatch loses the refresh/request race and 401s the
840
+ # egress (issue 214). Default: 300000 (5 minutes).
841
+ refresh_margin_ms: 300000
842
+
761
843
  # ─────────────────────────────────────────────────────────────────────────────
762
844
  # gondolin — microVM execution environment (Gondolin substrate).
763
845
  # ─────────────────────────────────────────────────────────────────────────────
764
846
  gondolin:
765
- # image (string | null): the agent rootfs the VM boots, expressed as a Gondolin
766
- # image selector. Build it ONCE with `npm run build:image` (see images/agents/) —
767
- # the build prints a content-addressed build id (a digest); pin that id here for
768
- # an immutable, reproducible reference. A `name:tag` ref (e.g.
769
- # `symphony-agents:latest`) or a path to an exported asset directory also work.
770
- # The image bakes a Node.js runtime, every ACP-capable coding agent
771
- # (claude-agent-acp, codex-acp, opencode), and the in-VM launcher at
772
- # /opt/symphony/vm-agent.mjs so dispatch needs no runtime mounts. REQUIRED:
773
- # the runner fails fast at boot when this is unset. Default: null.
774
- image: symphony-agents:latest
847
+ # image: the agent rootfs source. THREE forms, in precedence order
848
+ # (image > oci_image > managed):
849
+ #
850
+ # 1. `managed` (the DEFAULT when neither `image` nor `oci_image` is set, and what a
851
+ # fresh scaffold emits): FETCH a prebuilt Gondolin asset published to symphony's
852
+ # OWN GitHub releases for the running symphony version + host arch (issue 224).
853
+ # No docker, no `npm run build:image`, no local convert — onboarding is
854
+ # `scaffold run`. On the first poll the orchestrator downloads + verifies (against
855
+ # the published `.sha256`) + imports the asset ONCE (the dashboard shows the
856
+ # one-time fetch) and HOLDS VM dispatch until it's ready, then boots the cached
857
+ # asset. Published for x86_64 and arm64/aarch64 (the `release.yml` agent-image
858
+ # matrix); any OTHER arch makes the managed source inert and the doctor FAILs
859
+ # with an unsupported-arch message.
860
+ # Override `SYMPHONY_RELEASE_REPO` to fetch from a fork/staging release.
861
+ #
862
+ # 2. a PRE-CONVERTED asset selector — the low-level escape hatch. Build it ONCE with
863
+ # `npm run build:image` (see images/agents/); the build prints a content-addressed
864
+ # build id (a digest) — pin that here for an immutable reference. A `name:tag` ref
865
+ # (e.g. `symphony-agents:latest`) or a path to an exported asset directory also
866
+ # work. WINS over `oci_image` and the managed source when set.
867
+ #
868
+ # 3. `oci_image` (below): a normal OCI ref symphony auto-converts on first reconcile.
869
+ #
870
+ # The image ships only `mise`; node + the ACP CLIs (claude-agent-acp, codex-acp)
871
+ # are mise-installed at dispatch and the in-VM launcher is staged at
872
+ # /opt/symphony/vm-agent.mjs — so dispatch needs no runtime mounts. Default: managed.
873
+ image: managed
874
+
875
+ # oci_image (string | null): a NORMAL OCI image reference (a docker/podman repo
876
+ # string, optionally `:tag` or `@sha256:…`) that symphony auto-converts + caches into
877
+ # a Gondolin asset as part of the reconcile loop (issue 206) — no manual
878
+ # `npm run build:image` step. On the first poll where the asset isn't cached, the
879
+ # orchestrator converts it ONCE (the dashboard shows a "building microVM image
880
+ # (one-time setup)" banner) and HOLDS VM dispatch until it's ready; the cache key is
881
+ # the resolved OCI digest, re-checked cheaply each reconcile tick (a read-only LOCAL
882
+ # resolve, no pull), so a moving tag whose locally-resolved digest changes (e.g. after
883
+ # a re-pull) reconverts before dispatch resumes — readiness tracks the live digest, not
884
+ # a ref string that converted once. `oci_pull_policy` governs the one-time conversion
885
+ # pull, not this readiness check. Once converted, dispatch boots the cached asset
886
+ # automatically — set ONLY
887
+ # this key and go; you do NOT also need to set `image`. The MVP targets the "golden"
888
+ # path — a published ref that already bakes the guest contract (Node ≥ 21 + the ACP
889
+ # CLIs on PATH; the launcher is staged at dispatch). `image` stays the escape hatch
890
+ # for an asset you converted yourself, and WINS when both are set: when `image` is
891
+ # set the reconcile gate boots IT and the `oci_image` auto-convert path is bypassed
892
+ # entirely — dispatch is never held and no one-time build runs. Default: null.
893
+ # oci_image: ghcr.io/your-org/symphony-agents:1.0.0
894
+
895
+ # oci_pull_policy (if-not-present | always | never): how the OCI image is pulled
896
+ # while resolving its digest for the one-time conversion. `if-not-present` pulls
897
+ # only when the ref isn't already in the local container store; `always` re-pulls
898
+ # even when it IS present locally, so a moving tag (`repo:latest`) picks up its
899
+ # fresh registry digest (and reconverts when that digest changed); `never` never
900
+ # pulls and requires the image to already be in the local store. The cache key and
901
+ # the converted asset are always keyed on the POST-pull digest, so `always` can't
902
+ # cache against a stale local one. Only consulted when `oci_image` is set (and not
903
+ # by the per-tick readiness probe, which is always pull-free). Default: if-not-present.
904
+ # oci_pull_policy: if-not-present
905
+
906
+ # oci_runtime (docker | podman | null): container runtime used for the conversion
907
+ # pull/export. null = auto-detect (prefers docker, falls back to podman). Only
908
+ # consulted when `oci_image` is set. Default: null (auto-detect).
909
+ # oci_runtime: docker
775
910
 
776
911
  # cpus (int): vCPU count per VM. Default: 2.
777
912
  cpus: 2
@@ -779,6 +914,14 @@ gondolin:
779
914
  # mem_mib (int): RAM per VM in MiB. Default: 2048.
780
915
  mem_mib: 4096
781
916
 
917
+ # rootfs_size (string): guest rootfs virtual-disk size (qemu size syntax, e.g. "3G"),
918
+ # passed to the VM substrate as the MINIMUM rootfs size. The agent toolchain installs
919
+ # onto the guest ROOTFS each dispatch (chmod works there; the sandboxfs bind mount
920
+ # silently drops it — issue 222), so the rootfs must be big enough for the ephemeral
921
+ # install: node alone is ~123 MB plus the agent CLIs, and the default ~593 MB rootfs
922
+ # leaves too little headroom. Default: "3G".
923
+ # rootfs_size: 3G
924
+
782
925
  # volumes (list): additional host:guest VFS mounts beyond the auto-mounted
783
926
  # workspace. Gondolin's VFS is programmable (no hard per-VM mount cap), but keep
784
927
  # this lean — if ANY state sets `eval_mode: true` it adds two read-only mounts
@@ -800,6 +943,38 @@ gondolin:
800
943
  - OPENAI_API_KEY
801
944
  - ANTHROPIC_API_KEY
802
945
 
946
+ # mise (jdx/mise) toolchain provisioning. This is ALWAYS ON and is the ONLY toolchain
947
+ # path (issue 233) — there is no enable/disable toggle. The agent image ships ONLY the
948
+ # `mise` binary (+ glibc essentials); node + the agent CLIs (claude-code, codex,
949
+ # claude-agent-acp, codex-acp) come from a mise SYSTEM config symphony
950
+ # STAGES into the guest at /etc/mise/config.toml at dispatch, and a consuming repo's
951
+ # own `mise.toml` / `.tool-versions` (higher precedence) ADDS project toolchains
952
+ # (rust, go, kotlin+gradle, a different node, …) on top. `mise install` runs once
953
+ # per dispatch, installing the toolchain onto the guest ROOTFS at /opt/symphony/mise-data
954
+ # (EPHEMERAL per VM — see below); the agent runtime node is resolved system-scoped via
955
+ # `mise which node` (so a project mise.toml can't downgrade the agent below the Node ≥ 21
956
+ # the launcher needs). Bumping an agent CLI is a one-line edit to
957
+ # assets/symphony-mise.system.toml + a restart — NO image rebuild + digest repin.
958
+ #
959
+ # WHY the rootfs, not a bind mount (issue 222): the gondolin sandboxfs bind mount has no
960
+ # chmod opcode (chmod is a silent no-op there), so mise's create-then-chmod extractor +
961
+ # npm postinstall would produce NON-executable binaries on it; the guest rootfs (ext4)
962
+ # honors chmod, so installs land there executable. Grow it via `gondolin.rootfs_size`
963
+ # (default 3G) so the per-VM install fits. Only the pure downloads (node tarballs + the
964
+ # npm package cache) persist across dispatches, on the bind-mounted `data_dir` below.
965
+ #
966
+ # The only tunable here is `data_dir` (the download cache location).
967
+ mise:
968
+ # data_dir (string): host directory bind-mounted RW → /opt/symphony/mise-cache as the
969
+ # persistent mise DOWNLOAD CACHE (node tarballs + the npm package cache) — NOT the
970
+ # install dir (issue 222): installs need chmod, which the sandboxfs bind mount silently
971
+ # drops, so the toolchain installs onto the ephemeral guest rootfs and only the pure
972
+ # downloads live here (so a cold install isn't re-downloaded every dispatch). SHARED
973
+ # across every VM; host-isolated from your own ~/.local/share/mise. Absolute as-is,
974
+ # `~`/`$VAR`-expanded, relative → resolved against the workflow-file dir.
975
+ # Default: ~/.symphony/mise.
976
+ # data_dir: ~/.symphony/mise
977
+
803
978
  # ─────────────────────────────────────────────────────────────────────────────
804
979
  # egress — general dev-tooling firewall for the in-VM agent.
805
980
  #
@@ -819,12 +994,23 @@ gondolin:
819
994
  # ─────────────────────────────────────────────────────────────────────────────
820
995
  egress:
821
996
  # allowed_hosts (string[]): hostnames the in-VM agent may reach for dev tooling.
822
- # Default: [] (no extra hosts the agent can reach only its inference host).
823
- # Bare hostnames ONLY no scheme, port, or path (`github.com`, not
997
+ # Default: the mise provisioning hosts [nodejs.org, registry.npmjs.org, mise.jdx.dev,
998
+ # mise-versions.jdx.dev] ALWAYS, since mise provisioning is unconditional (issue 233)
999
+ # — so `mise install` of the staged SYSTEM config isn't firewall-blocked. Bare
1000
+ # hostnames ONLY — no scheme, port, or path (`github.com`, not
824
1001
  # `https://github.com/...`). A malformed entry fails safe (the host simply stays
825
1002
  # blocked, never opened). Each entry is matched against the request host exactly.
1003
+ #
1004
+ # An explicit list REPLACES the default — so if you set `allowed_hosts`, re-list all
1005
+ # four mise hosts here alongside your own (omitting mise-versions.jdx.dev re-hits the
1006
+ # cold-cache node@<N> resolution failure issue 223 documents). PROJECT toolchains pull
1007
+ # from their own hosts; add those too (rust → static.rust-lang.org, gradle →
1008
+ # services.gradle.org, JDK → api.adoptium.net, …).
826
1009
  allowed_hosts:
827
- - registry.npmjs.org # npm install
1010
+ - nodejs.org # mise node prebuilts
1011
+ - registry.npmjs.org # npm install + mise npm: backend (agent CLIs)
1012
+ - mise.jdx.dev # mise registry redirect
1013
+ - mise-versions.jdx.dev # mise precompiled tool version lists (node@N, etc.)
828
1014
  - github.com # git-based deps / release pages
829
1015
  - codeload.github.com # GitHub tarball fetch
830
1016
  - objects.githubusercontent.com # release-binary downloads
@@ -833,9 +1019,13 @@ egress:
833
1019
  # server — HTTP dashboard + MCP endpoint listener.
834
1020
  # ─────────────────────────────────────────────────────────────────────────────
835
1021
  server:
836
- # port (int | null): when null, no HTTP server is started. `--port <n>` on
837
- # the CLI overrides this. Default: null.
838
- port: 8787
1022
+ # port (int | null): HTTP listener port. `--port <n>` on the CLI overrides
1023
+ # this. When both are unset symphony binds an ephemeral port (0 → the kernel
1024
+ # picks a free port), so it "just works" with no config and two concurrent
1025
+ # instances never collide; the actually-bound port + dashboard URL are printed
1026
+ # to stdout at startup. Set a value here to pin a fixed port. Default: null
1027
+ # (ephemeral).
1028
+ # port: 8787
839
1029
 
840
1030
  # host (string): bind address. Default: '127.0.0.1'. Bind to '0.0.0.0' only
841
1031
  # inside a trusted network boundary; the dashboard has no built-in auth.
@@ -880,68 +1070,31 @@ mcp:
880
1070
  # orchestrator through the host gateway (e.g. bridge networking with a
881
1071
  # fixed reverse-proxy URL). Default: null.
882
1072
  host_url: null
883
- ---
884
- <!--
885
- Liquid-templated prompt body. Rendered once per dispatched issue. Context:
886
-
887
- issue.identifier — the issue's external id (e.g. "DEMO-42").
888
- issue.title — issue title (string).
889
- issue.state — current state (string, matches a key in `states:`).
890
- issue.description — body text (string or empty). `symphony.transition`
891
- appends its `notes` block here before the file moves,
892
- so the next state's agent reads the previous state's
893
- handoff message verbatim.
894
- issue.priority — number or null.
895
- issue.labels list of strings (lowercased).
896
- attempt int, 1-based attempt counter; absent on first attempt.
897
-
898
- Available Liquid filters: standard Shopify Liquid plus `escape_once`.
899
-
900
- Per-state prompt branching (V1 pattern): when `states:` declares more than
901
- one active role (e.g. Todo + Review), wrap the state-specific instructions in
902
- a `{% case issue.state %}` / `{% when "..." %}` / `{% else %}` block. The
903
- runner renders the prompt fresh on every dispatch, so each state's agent sees
904
- only its own instructions plus whatever common preamble / postamble lives
905
- outside the case. See WORKFLOW.md in this repo for a worked example.
906
-
907
- The body below is the literal prompt sent to the agent. Keep it specific to
908
- this workflow; orchestrator behavior (transition, request_human_steering,
909
- propose_issue) is the same no matter what you write here.
910
- -->
911
-
912
- You are picking up a single issue and shepherding it through the workflow.
913
-
914
- Issue: **{{ issue.identifier }} — {{ issue.title }}**
915
- State: {{ issue.state }}
916
- {% if issue.priority -%}Priority: {{ issue.priority }}{%- endif %}
917
- {% if issue.labels.size > 0 -%}Labels: {% for l in issue.labels %}{{ l }}{% unless forloop.last %}, {% endunless %}{% endfor %}{%- endif %}
918
-
919
- {% if issue.description -%}
920
- Description:
921
-
922
- {{ issue.description }}
923
- {%- endif %}
924
-
925
- Goals:
926
-
927
- 1. Work in the current directory only; treat it as the issue workspace.
928
- 2. Make the smallest correct change that satisfies the issue.
929
- 3. Hand off when done. `symphony.transition({ to_state, notes? })` is the
930
- canonical (and only) exit verb: pass a declared state name and optional
931
- markdown notes that get appended to the issue body for the next agent.
932
- For single-agent workflows, transition straight into the first declared
933
- `role: terminal` state to end the run.
934
- 4. If you cannot proceed without human input, call
935
- `symphony.request_human_steering({ question, context? })`. Your turn ends
936
- immediately; the human's reply arrives as your next prompt.
937
- 5. If you notice work out of scope for this issue — unrelated bugs, follow-ups
938
- a human should size, refactors worth a separate dispatch — call
939
- `symphony.propose_issue({ title, description?, labels?, priority? })`. It
940
- lands in the first declared `role: holding` state directory (defaults to
941
- `Triage/`); the operator approves or discards. Do not graft unrelated
942
- edits onto this branch.
943
-
944
- {% if attempt -%}
945
- This is continuation/retry attempt {{ attempt }}. Inspect the workspace before
946
- making new edits; your previous run may have left state behind.
947
- {%- endif %}
1073
+
1074
+ # ─────────────────────────────────────────────────────────────────────────────
1075
+ # prompts — per-state files (there is NO inline prompt body).
1076
+ #
1077
+ # A workflow file is pure YAML config; it carries no prompt body. Each active
1078
+ # state's prompt is its `states.<name>.prompt_file` (a Liquid template) wrapped
1079
+ # in the shared `prompt.preamble_file` / `prompt.footer_file` (see the `prompt:`
1080
+ # and `states:` sections above). The runner renders the matching state's
1081
+ # assembled template fresh on every dispatch, so each state's agent sees only its
1082
+ # own instructions plus the shared preamble / footer.
1083
+ #
1084
+ # Liquid context available to every prompt file ({ issue, attempt }):
1085
+ # issue.identifier the issue's external id (e.g. "DEMO-42").
1086
+ # issue.title issue title (string).
1087
+ # issue.state — current state (string, matches a key in `states:`).
1088
+ # issue.description — body text (string or empty). `symphony.transition`
1089
+ # appends its `notes` block here before the file moves, so
1090
+ # the next state's agent reads the previous state's handoff
1091
+ # message verbatim.
1092
+ # issue.priority — number or null.
1093
+ # issue.labels — list of strings (lowercased).
1094
+ # attempt — int, 1-based attempt counter; absent on first attempt.
1095
+ #
1096
+ # Available Liquid filters: standard Shopify Liquid plus `escape_once`.
1097
+ #
1098
+ # See prompts/ alongside WORKFLOW.yaml in this repo for worked prompt files, and
1099
+ # WORKFLOW.minimal.yaml for the smallest single-state example.
1100
+ # ─────────────────────────────────────────────────────────────────────────────