create-flowdular 0.2.4 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (269) hide show
  1. package/README.md +11 -0
  2. package/agent-template/.agents/skills/agent-tool-design/SKILL.md +203 -0
  3. package/agent-template/.agents/skills/auth-security-review/SKILL.md +90 -0
  4. package/agent-template/.agents/skills/auto-review/SKILL.md +103 -0
  5. package/agent-template/.agents/skills/bug-hunt/SKILL.md +104 -0
  6. package/agent-template/.agents/skills/business-agent-design/SKILL.md +182 -0
  7. package/agent-template/.agents/skills/cli-extension/SKILL.md +108 -0
  8. package/agent-template/.agents/skills/core-extend/SKILL.md +99 -0
  9. package/agent-template/.agents/skills/database-adapter/SKILL.md +198 -0
  10. package/agent-template/.agents/skills/database-adapter/references/first-run-and-matrix.md +105 -0
  11. package/agent-template/.agents/skills/migration-authoring/SKILL.md +161 -0
  12. package/agent-template/.agents/skills/module-new/SKILL.md +171 -0
  13. package/agent-template/.agents/skills/module-update/SKILL.md +91 -0
  14. package/agent-template/.agents/skills/perf-audit/SKILL.md +98 -0
  15. package/agent-template/.agents/skills/release-eject-pr/SKILL.md +107 -0
  16. package/agent-template/.agents/skills/spec-approval/SKILL.md +106 -0
  17. package/agent-template/.agents/skills/test-hardening/SKILL.md +79 -0
  18. package/agent-template/.agents/skills/translations-i18n/SKILL.md +78 -0
  19. package/agent-template/.agents/skills/ux-design/SKILL.md +92 -0
  20. package/agent-template/.agents/skills/variables/SKILL.md +156 -0
  21. package/agent-template/.agents/skills/workflow-development/SKILL.md +192 -0
  22. package/agent-template/.ai/README.md +62 -0
  23. package/agent-template/.ai/agents/README.md +27 -0
  24. package/agent-template/.ai/agents/module-executor.md +36 -0
  25. package/agent-template/.ai/agents/reviewer.md +23 -0
  26. package/agent-template/.ai/agents/sandbox/agentic-engineer.md +31 -0
  27. package/agent-template/.ai/agents/sandbox/backend-engineer.md +36 -0
  28. package/agent-template/.ai/agents/sandbox/business-manager.md +23 -0
  29. package/agent-template/.ai/agents/sandbox/frontend-engineer.md +27 -0
  30. package/agent-template/.ai/agents/sandbox/ux-designer.md +23 -0
  31. package/agent-template/.ai/agents/spec-author.md +29 -0
  32. package/agent-template/.ai/blueprints/add-migration/README.md +5 -0
  33. package/agent-template/.ai/blueprints/add-migration/allowed-paths.yaml +23 -0
  34. package/agent-template/.ai/blueprints/add-migration/blueprint.json +14 -0
  35. package/agent-template/.ai/blueprints/add-migration/examples/invalid/input-destructive.json +6 -0
  36. package/agent-template/.ai/blueprints/add-migration/examples/invalid/plan-unnumbered-file.json +9 -0
  37. package/agent-template/.ai/blueprints/add-migration/examples/valid/input.json +6 -0
  38. package/agent-template/.ai/blueprints/add-migration/examples/valid/plan.json +9 -0
  39. package/agent-template/.ai/blueprints/add-migration/gates.yaml +30 -0
  40. package/agent-template/.ai/blueprints/add-migration/input.schema.json +23 -0
  41. package/agent-template/.ai/blueprints/add-migration/plan.schema.json +54 -0
  42. package/agent-template/.ai/blueprints/add-migration/required-files.yaml +18 -0
  43. package/agent-template/.ai/blueprints/add-migration/spec-requirements.yaml +13 -0
  44. package/agent-template/.ai/blueprints/add-migration/steps.yaml +62 -0
  45. package/agent-template/.ai/blueprints/author-spec/README.md +5 -0
  46. package/agent-template/.ai/blueprints/author-spec/allowed-paths.yaml +7 -0
  47. package/agent-template/.ai/blueprints/author-spec/blueprint.json +14 -0
  48. package/agent-template/.ai/blueprints/author-spec/examples/invalid/input-missing-outcome.json +5 -0
  49. package/agent-template/.ai/blueprints/author-spec/examples/valid/input.json +6 -0
  50. package/agent-template/.ai/blueprints/author-spec/gates.yaml +13 -0
  51. package/agent-template/.ai/blueprints/author-spec/input.schema.json +20 -0
  52. package/agent-template/.ai/blueprints/author-spec/plan.schema.json +14 -0
  53. package/agent-template/.ai/blueprints/author-spec/required-files.yaml +6 -0
  54. package/agent-template/.ai/blueprints/author-spec/spec-requirements.yaml +35 -0
  55. package/agent-template/.ai/blueprints/author-spec/steps.yaml +28 -0
  56. package/agent-template/.ai/blueprints/author-spec/templates/module.yaml +45 -0
  57. package/agent-template/.ai/blueprints/bug-fix/README.md +5 -0
  58. package/agent-template/.ai/blueprints/bug-fix/allowed-paths.yaml +29 -0
  59. package/agent-template/.ai/blueprints/bug-fix/blueprint.json +14 -0
  60. package/agent-template/.ai/blueprints/bug-fix/examples/invalid/input-no-symptom.json +4 -0
  61. package/agent-template/.ai/blueprints/bug-fix/examples/invalid/plan-no-test.json +8 -0
  62. package/agent-template/.ai/blueprints/bug-fix/examples/valid/input.json +6 -0
  63. package/agent-template/.ai/blueprints/bug-fix/examples/valid/plan.json +8 -0
  64. package/agent-template/.ai/blueprints/bug-fix/gates.yaml +30 -0
  65. package/agent-template/.ai/blueprints/bug-fix/input.schema.json +25 -0
  66. package/agent-template/.ai/blueprints/bug-fix/plan.schema.json +53 -0
  67. package/agent-template/.ai/blueprints/bug-fix/required-files.yaml +7 -0
  68. package/agent-template/.ai/blueprints/bug-fix/spec-requirements.yaml +7 -0
  69. package/agent-template/.ai/blueprints/bug-fix/steps.yaml +51 -0
  70. package/agent-template/.ai/blueprints/core-extend/README.md +5 -0
  71. package/agent-template/.ai/blueprints/core-extend/allowed-paths.yaml +49 -0
  72. package/agent-template/.ai/blueprints/core-extend/blueprint.json +14 -0
  73. package/agent-template/.ai/blueprints/core-extend/examples/invalid/input-unknown-package.json +5 -0
  74. package/agent-template/.ai/blueprints/core-extend/examples/invalid/plan-missing-gates.json +7 -0
  75. package/agent-template/.ai/blueprints/core-extend/examples/valid/input.json +6 -0
  76. package/agent-template/.ai/blueprints/core-extend/examples/valid/plan.json +10 -0
  77. package/agent-template/.ai/blueprints/core-extend/gates.yaml +16 -0
  78. package/agent-template/.ai/blueprints/core-extend/input.schema.json +54 -0
  79. package/agent-template/.ai/blueprints/core-extend/plan.schema.json +39 -0
  80. package/agent-template/.ai/blueprints/core-extend/required-files.yaml +36 -0
  81. package/agent-template/.ai/blueprints/core-extend/spec-requirements.yaml +17 -0
  82. package/agent-template/.ai/blueprints/core-extend/steps.yaml +54 -0
  83. package/agent-template/.ai/blueprints/edit-module/README.md +9 -0
  84. package/agent-template/.ai/blueprints/edit-module/allowed-paths.yaml +27 -0
  85. package/agent-template/.ai/blueprints/edit-module/blueprint.json +20 -0
  86. package/agent-template/.ai/blueprints/edit-module/examples/invalid/input-unknown-change.json +5 -0
  87. package/agent-template/.ai/blueprints/edit-module/examples/invalid/plan-touches-platform.json +15 -0
  88. package/agent-template/.ai/blueprints/edit-module/examples/valid/input.json +5 -0
  89. package/agent-template/.ai/blueprints/edit-module/examples/valid/plan.json +25 -0
  90. package/agent-template/.ai/blueprints/edit-module/gates.yaml +30 -0
  91. package/agent-template/.ai/blueprints/edit-module/input.schema.json +31 -0
  92. package/agent-template/.ai/blueprints/edit-module/plan.schema.json +65 -0
  93. package/agent-template/.ai/blueprints/edit-module/required-files.yaml +80 -0
  94. package/agent-template/.ai/blueprints/edit-module/spec-requirements.yaml +15 -0
  95. package/agent-template/.ai/blueprints/edit-module/steps.yaml +115 -0
  96. package/agent-template/.ai/blueprints/new-module/README.md +7 -0
  97. package/agent-template/.ai/blueprints/new-module/allowed-paths.yaml +27 -0
  98. package/agent-template/.ai/blueprints/new-module/blueprint.json +20 -0
  99. package/agent-template/.ai/blueprints/new-module/examples/invalid/input-spec-outside-modules.json +4 -0
  100. package/agent-template/.ai/blueprints/new-module/examples/invalid/plan-unknown-gate.json +8 -0
  101. package/agent-template/.ai/blueprints/new-module/examples/valid/input.json +5 -0
  102. package/agent-template/.ai/blueprints/new-module/examples/valid/plan.json +22 -0
  103. package/agent-template/.ai/blueprints/new-module/gates.yaml +30 -0
  104. package/agent-template/.ai/blueprints/new-module/input.schema.json +21 -0
  105. package/agent-template/.ai/blueprints/new-module/plan.schema.json +58 -0
  106. package/agent-template/.ai/blueprints/new-module/required-files.yaml +73 -0
  107. package/agent-template/.ai/blueprints/new-module/spec-requirements.yaml +30 -0
  108. package/agent-template/.ai/blueprints/new-module/steps.yaml +138 -0
  109. package/agent-template/.ai/blueprints/release/README.md +5 -0
  110. package/agent-template/.ai/blueprints/release/allowed-paths.yaml +19 -0
  111. package/agent-template/.ai/blueprints/release/blueprint.json +14 -0
  112. package/agent-template/.ai/blueprints/release/examples/invalid/input-bad-version.json +4 -0
  113. package/agent-template/.ai/blueprints/release/examples/invalid/plan-bad-branch.json +7 -0
  114. package/agent-template/.ai/blueprints/release/examples/valid/input.json +5 -0
  115. package/agent-template/.ai/blueprints/release/examples/valid/plan.json +20 -0
  116. package/agent-template/.ai/blueprints/release/gates.yaml +20 -0
  117. package/agent-template/.ai/blueprints/release/input.schema.json +24 -0
  118. package/agent-template/.ai/blueprints/release/plan.schema.json +46 -0
  119. package/agent-template/.ai/blueprints/release/required-files.yaml +19 -0
  120. package/agent-template/.ai/blueprints/release/spec-requirements.yaml +8 -0
  121. package/agent-template/.ai/blueprints/release/steps.yaml +47 -0
  122. package/agent-template/.ai/blueprints/security-review/README.md +5 -0
  123. package/agent-template/.ai/blueprints/security-review/allowed-paths.yaml +6 -0
  124. package/agent-template/.ai/blueprints/security-review/blueprint.json +14 -0
  125. package/agent-template/.ai/blueprints/security-review/examples/invalid/input-unknown-kind.json +4 -0
  126. package/agent-template/.ai/blueprints/security-review/examples/invalid/plan-finding-without-scenario.json +14 -0
  127. package/agent-template/.ai/blueprints/security-review/examples/valid/input.json +4 -0
  128. package/agent-template/.ai/blueprints/security-review/examples/valid/plan.json +19 -0
  129. package/agent-template/.ai/blueprints/security-review/gates.yaml +22 -0
  130. package/agent-template/.ai/blueprints/security-review/input.schema.json +20 -0
  131. package/agent-template/.ai/blueprints/security-review/plan.schema.json +65 -0
  132. package/agent-template/.ai/blueprints/security-review/required-files.yaml +6 -0
  133. package/agent-template/.ai/blueprints/security-review/spec-requirements.yaml +9 -0
  134. package/agent-template/.ai/blueprints/security-review/steps.yaml +38 -0
  135. package/agent-template/.ai/examples/README.md +8 -0
  136. package/agent-template/.ai/examples/bad/client-imports-server/README.md +20 -0
  137. package/agent-template/.ai/examples/bad/client-imports-server/api.ts +12 -0
  138. package/agent-template/.ai/examples/bad/missing-acl/README.md +23 -0
  139. package/agent-template/.ai/examples/bad/missing-acl/endpoints.ts +12 -0
  140. package/agent-template/.ai/examples/bad/tenant-from-body/README.md +19 -0
  141. package/agent-template/.ai/examples/bad/tenant-from-body/endpoints.ts +33 -0
  142. package/agent-template/.ai/examples/client-contribution/CustomerListView.tsrx +34 -0
  143. package/agent-template/.ai/examples/client-contribution/README.md +11 -0
  144. package/agent-template/.ai/examples/client-contribution/contribution.tsrx +48 -0
  145. package/agent-template/.ai/examples/client-contribution/index.ts +20 -0
  146. package/agent-template/.ai/examples/client-contribution/permissions.ts +8 -0
  147. package/agent-template/.ai/examples/customer-cli-extension/README.md +14 -0
  148. package/agent-template/.ai/examples/customer-cli-extension/commands.json +17 -0
  149. package/agent-template/.ai/examples/customer-cli-extension/index.ts +36 -0
  150. package/agent-template/.ai/examples/module-create/task-packet.json +11 -0
  151. package/agent-template/.ai/guides/application-development.md +97 -0
  152. package/agent-template/.ai/policies/capabilities.yaml +164 -0
  153. package/agent-template/.ai/policies/model-routing.yaml +72 -0
  154. package/agent-template/.ai/policies/path-ownership.yaml +65 -0
  155. package/agent-template/.ai/policies/task-budgets.yaml +37 -0
  156. package/agent-template/.ai/references/catalog/LICENSE +21 -0
  157. package/agent-template/.ai/references/catalog/migrations/0001_catalog_core.down.sql +2 -0
  158. package/agent-template/.ai/references/catalog/migrations/0001_catalog_core.up.sql +21 -0
  159. package/agent-template/.ai/references/catalog/migrations/0002_catalog_history.down.sql +3 -0
  160. package/agent-template/.ai/references/catalog/migrations/0002_catalog_history.up.sql +20 -0
  161. package/agent-template/.ai/references/catalog/migrations/0003_catalog_history_service_actors.down.sql +3 -0
  162. package/agent-template/.ai/references/catalog/migrations/0003_catalog_history_service_actors.up.sql +36 -0
  163. package/agent-template/.ai/references/catalog/migrations/0004_catalog_idempotency_ledger.down.sql +3 -0
  164. package/agent-template/.ai/references/catalog/migrations/0004_catalog_idempotency_ledger.up.sql +19 -0
  165. package/agent-template/.ai/references/catalog/migrations/README.md +3 -0
  166. package/agent-template/.ai/references/catalog/module.json +27 -0
  167. package/agent-template/.ai/references/catalog/package.json +49 -0
  168. package/agent-template/.ai/references/catalog/spec/module.yaml +86 -0
  169. package/agent-template/.ai/references/catalog/src/acl/permissions.ts +6 -0
  170. package/agent-template/.ai/references/catalog/src/agent/tools.ts +164 -0
  171. package/agent-template/.ai/references/catalog/src/api/endpoints.ts +243 -0
  172. package/agent-template/.ai/references/catalog/src/client/CatalogHistoryDrawer.tsrx +123 -0
  173. package/agent-template/.ai/references/catalog/src/client/CatalogItemForm.tsrx +190 -0
  174. package/agent-template/.ai/references/catalog/src/client/CatalogView.tsrx +473 -0
  175. package/agent-template/.ai/references/catalog/src/client/api.ts +111 -0
  176. package/agent-template/.ai/references/catalog/src/client/contribution.tsrx +61 -0
  177. package/agent-template/.ai/references/catalog/src/client/index.ts +18 -0
  178. package/agent-template/.ai/references/catalog/src/client/navigation-copy.ts +9 -0
  179. package/agent-template/.ai/references/catalog/src/client/state.ts +24 -0
  180. package/agent-template/.ai/references/catalog/src/domain/types.ts +32 -0
  181. package/agent-template/.ai/references/catalog/src/domain/variables.ts +111 -0
  182. package/agent-template/.ai/references/catalog/src/index.ts +31 -0
  183. package/agent-template/.ai/references/catalog/src/platform.ts +35 -0
  184. package/agent-template/.ai/references/catalog/src/server/index.ts +4 -0
  185. package/agent-template/.ai/references/catalog/src/server/runtime.ts +86 -0
  186. package/agent-template/.ai/references/catalog/src/services/catalog-service.ts +306 -0
  187. package/agent-template/.ai/references/catalog/src/services/database-repository.ts +440 -0
  188. package/agent-template/.ai/references/catalog/src/services/index.ts +4 -0
  189. package/agent-template/.ai/references/catalog/src/services/migration.ts +171 -0
  190. package/agent-template/.ai/references/catalog/src/services/repository.ts +36 -0
  191. package/agent-template/.ai/references/catalog/src/services/target-idempotency.ts +59 -0
  192. package/agent-template/.ai/references/catalog/tests/agent-tools.test.ts +277 -0
  193. package/agent-template/.ai/references/catalog/tests/endpoints.test.ts +320 -0
  194. package/agent-template/.ai/references/catalog/tests/idempotency.test.ts +297 -0
  195. package/agent-template/.ai/references/catalog/tests/migrations.test.ts +149 -0
  196. package/agent-template/.ai/references/catalog/tests/module.test.ts +271 -0
  197. package/agent-template/.ai/references/catalog/tests/support/database.ts +76 -0
  198. package/agent-template/.ai/references/catalog/translations/en.json +101 -0
  199. package/agent-template/.ai/references/catalog/translations/pl.json +101 -0
  200. package/agent-template/.ai/references/catalog/tsconfig.json +15 -0
  201. package/agent-template/.ai/references/catalog/vitest.config.ts +16 -0
  202. package/agent-template/.ai/references/catalog.provenance.json +55 -0
  203. package/agent-template/.ai/rules/flowdular.md +86 -0
  204. package/agent-template/.ai/skills/README.md +36 -0
  205. package/agent-template/.ai/skills/agent-tool-design/SKILL.md +209 -0
  206. package/agent-template/.ai/skills/auth-security-review/SKILL.md +96 -0
  207. package/agent-template/.ai/skills/auto-review/SKILL.md +112 -0
  208. package/agent-template/.ai/skills/bug-hunt/SKILL.md +110 -0
  209. package/agent-template/.ai/skills/business-agent-design/SKILL.md +188 -0
  210. package/agent-template/.ai/skills/cli-extension/SKILL.md +114 -0
  211. package/agent-template/.ai/skills/core-extend/SKILL.md +104 -0
  212. package/agent-template/.ai/skills/database-adapter/SKILL.md +204 -0
  213. package/agent-template/.ai/skills/database-adapter/references/first-run-and-matrix.md +105 -0
  214. package/agent-template/.ai/skills/migration-authoring/SKILL.md +167 -0
  215. package/agent-template/.ai/skills/module-new/SKILL.md +180 -0
  216. package/agent-template/.ai/skills/module-update/SKILL.md +100 -0
  217. package/agent-template/.ai/skills/perf-audit/SKILL.md +105 -0
  218. package/agent-template/.ai/skills/release-eject-pr/SKILL.md +113 -0
  219. package/agent-template/.ai/skills/spec-approval/SKILL.md +112 -0
  220. package/agent-template/.ai/skills/test-hardening/SKILL.md +86 -0
  221. package/agent-template/.ai/skills/translations-i18n/SKILL.md +85 -0
  222. package/agent-template/.ai/skills/ux-design/SKILL.md +97 -0
  223. package/agent-template/.ai/skills/variables/SKILL.md +164 -0
  224. package/agent-template/.ai/skills/workflow-development/SKILL.md +199 -0
  225. package/agent-template/.claude/skills/agent-tool-design/SKILL.md +203 -0
  226. package/agent-template/.claude/skills/auth-security-review/SKILL.md +90 -0
  227. package/agent-template/.claude/skills/auto-review/SKILL.md +103 -0
  228. package/agent-template/.claude/skills/bug-hunt/SKILL.md +104 -0
  229. package/agent-template/.claude/skills/business-agent-design/SKILL.md +182 -0
  230. package/agent-template/.claude/skills/cli-extension/SKILL.md +108 -0
  231. package/agent-template/.claude/skills/core-extend/SKILL.md +99 -0
  232. package/agent-template/.claude/skills/database-adapter/SKILL.md +198 -0
  233. package/agent-template/.claude/skills/database-adapter/references/first-run-and-matrix.md +105 -0
  234. package/agent-template/.claude/skills/migration-authoring/SKILL.md +161 -0
  235. package/agent-template/.claude/skills/module-new/SKILL.md +171 -0
  236. package/agent-template/.claude/skills/module-update/SKILL.md +91 -0
  237. package/agent-template/.claude/skills/perf-audit/SKILL.md +98 -0
  238. package/agent-template/.claude/skills/release-eject-pr/SKILL.md +107 -0
  239. package/agent-template/.claude/skills/spec-approval/SKILL.md +106 -0
  240. package/agent-template/.claude/skills/test-hardening/SKILL.md +79 -0
  241. package/agent-template/.claude/skills/translations-i18n/SKILL.md +78 -0
  242. package/agent-template/.claude/skills/ux-design/SKILL.md +92 -0
  243. package/agent-template/.claude/skills/variables/SKILL.md +156 -0
  244. package/agent-template/.claude/skills/workflow-development/SKILL.md +192 -0
  245. package/agent-template/AGENTS.md +77 -0
  246. package/agent-template/CLAUDE.md +77 -0
  247. package/agent-template/docs/adr/0001-development-reload.md +16 -0
  248. package/agent-template/docs/adr/0002-durable-agent-execution.md +21 -0
  249. package/agent-template/docs/adr/0003-module-settings.md +22 -0
  250. package/agent-template/docs/adr/0004-enterprise-access-and-audit.md +36 -0
  251. package/agent-template/docs/adr/0005-sandbox-runtime-and-coding-agents.md +81 -0
  252. package/agent-template/docs/adr/0006-agentic-workflows.md +1702 -0
  253. package/agent-template/docs/adr/0007-module-owned-agents.md +429 -0
  254. package/agent-template/docs/adr/0008-database-adapter-contract.md +90 -0
  255. package/agent-template/docs/agent-contract.md +45 -0
  256. package/agent-template/docs/configuration.md +122 -0
  257. package/agent-template/docs/database-adapters.md +346 -0
  258. package/agent-template/docs/design-system.md +217 -0
  259. package/agent-template/docs/modules.md +146 -0
  260. package/agent-template/platform/scripts/build.mjs +38 -0
  261. package/agent-template/rulesync.jsonc +11 -0
  262. package/dist/bin.js +3 -1
  263. package/package.json +3 -2
  264. package/template/default/.prettierignore +9 -0
  265. package/template/default/README.md +12 -0
  266. package/template/default/flowdular.json +3 -3
  267. package/template/default/package.json +6 -2
  268. package/template/default/platform/octane.config.ts +17 -6
  269. package/template/default/platform/package.json +2 -1
@@ -0,0 +1,1702 @@
1
+ # ADR 0006: Durable agentic workflows
2
+
3
+ - Status: proposed
4
+ - Date: 2026-09-02
5
+ - Decision owner: workflows.core
6
+
7
+ ## Context
8
+
9
+ Flowdular can execute one durable agent run and can trigger an agent from
10
+ `automations.core`. It cannot describe, publish, inspect, or recover a business
11
+ process that coordinates several pinned agents, deterministic decisions,
12
+ validated data, and module actions.
13
+
14
+ The requested product is a visual workflow builder similar in interaction to
15
+ an automation canvas. A workflow passes typed data through connected nodes,
16
+ shows the path taken, and can be called from another module. The engine must
17
+ remain inside the same tenant, permission, audit, idempotency, and durability
18
+ boundaries as direct agent execution.
19
+
20
+ This ADR defines the contract before implementation. The companion module spec
21
+ is `modules/workflows/spec/module.yaml` and remains `draft`.
22
+
23
+ ## Challenge verdict
24
+
25
+ ### Strongest case for the feature
26
+
27
+ The platform already has concrete consumers:
28
+
29
+ 1. A business module needs to run a repeatable multi-agent process without
30
+ copying orchestration logic into its service.
31
+ 2. `automations.core` needs a future target richer than one agent while keeping
32
+ schedule and webhook ownership outside the workflow engine.
33
+ 3. An operator needs to inspect one execution across several child agents,
34
+ deterministic gates, validation, and business actions.
35
+ 4. Agents and modules need one published, versioned callable artifact instead
36
+ of prompt conventions that exist only in one screen.
37
+
38
+ Doing nothing leaves every consumer to invent its own queue, state machine,
39
+ recovery, audit, data mapping, and visual history. The problem is real and is
40
+ not solved by the current `agents.run-queue` capability.
41
+
42
+ ### Attacks on the proposal
43
+
44
+ - **Necessity:** A simple sequence of agents could be hardcoded in each module.
45
+ That works for one flow, but it immediately duplicates recovery, audit, and
46
+ versioning. The second named consumer, automations, proves a shared seam is
47
+ needed.
48
+ - **Placement:** Putting graphs in `agents.core` would make every agent install
49
+ pay for a canvas, workflow database, and workflow worker. Putting them in
50
+ `automations.core` would incorrectly make clocks and webhooks prerequisites
51
+ for manual and module-invoked workflows. A separate optional module is the
52
+ owning layer.
53
+ - **Cost:** Validation is `O(V + E)` in time and space for `V` nodes and `E`
54
+ edges. A live run stores `O(V + E + A)` evidence, where `A` is total attempts.
55
+ When the module is absent, consumers pay one capability lookup and no worker,
56
+ timer, table, or client bundle cost.
57
+ - **Failure blast radius:** A duplicate action or a scope bypass can mutate
58
+ business data. The engine therefore admits only versioned registered actions,
59
+ requires idempotency, and cannot execute arbitrary code.
60
+ - **Reversibility:** Published graph JSON, node identifiers, action versions,
61
+ and run evidence are durable formats. They are one-way contracts. Version one
62
+ is deliberately smaller than a general process language.
63
+ - **Consistency:** The design follows the capability registry, agent run
64
+ leases, immutable permission snapshots, variable templates, module migration
65
+ ledger, actor model, and module-local audit pattern already in the repository.
66
+
67
+ ### Decision
68
+
69
+ Build the simpler alternative: an optional `workflows.core` module with a
70
+ versioned DAG engine. Version one has no graph cycles, arbitrary JavaScript,
71
+ dynamic code, arbitrary HTTP nodes, sub-workflow nodes, or parallel scheduling
72
+ guarantee.
73
+
74
+ The strongest surviving objection is that the current agent contract cannot
75
+ execute an exact historical agent revision, and the current registered tool
76
+ contract has no version or workflow-safe idempotency contract. Those are hard
77
+ prerequisites. Live publication must remain unavailable until `agents.core`
78
+ provides them. The canvas, validation, dry-run, and fixture simulation can land
79
+ first without weakening that refusal.
80
+
81
+ A spike changes this verdict only if it proves all three cases:
82
+
83
+ 1. Recovery after process loss between an agent enqueue and node settlement
84
+ produces one child run.
85
+ 2. Recovery after process loss around an action produces one business mutation.
86
+ 3. A 100-node, 200-edge graph validates, simulates, pages its history, and
87
+ resumes its stream within the declared limits.
88
+
89
+ ## Module boundary
90
+
91
+ `workflows.core` owns:
92
+
93
+ - workflow identities and lifecycle;
94
+ - mutable drafts and immutable published revisions;
95
+ - graph schemas, mappings, layout, and compiled plans;
96
+ - durable workflow and node execution state;
97
+ - edge transfer evidence and ordered workflow events;
98
+ - workflow-local audit evidence and run history;
99
+ - safe redacted payload snapshots and cost aggregation.
100
+
101
+ It does not own:
102
+
103
+ - agent definitions, providers, child agent runs, or model pricing;
104
+ - module business records or repositories;
105
+ - schedules, webhook definitions, trigger secrets, or a polling clock;
106
+ - user accounts, sessions, memberships, or source permissions;
107
+ - arbitrary connectors, shell execution, or downloaded code.
108
+
109
+ `workflows.core` depends on `agents.core`. It calls agents and registered actions
110
+ only through public capabilities. It never reads the agents database.
111
+
112
+ `automations.core` remains optional and separate. The workflow module never
113
+ starts an automation clock. A later small integration module can depend on
114
+ both modules and expose workflow targets to schedules and webhooks without
115
+ making either base module own the other.
116
+
117
+ ## Concrete consumers and call sites
118
+
119
+ ### A business module invokes a published workflow
120
+
121
+ A module that requires workflow support declares `workflows.core` as a module
122
+ dependency and imports the public server contract. It gets the capability
123
+ inside a protected endpoint or service that already has a trusted principal.
124
+
125
+ ```ts
126
+ import {
127
+ WORKFLOW_EXECUTION_CAPABILITY,
128
+ type WorkflowExecutionCapability,
129
+ } from '@flowdular/sdk/modules/workflows/server';
130
+ import { userActor } from '@flowdular/sdk/kernel';
131
+
132
+ const workflows = context.capabilities.get<WorkflowExecutionCapability>(
133
+ WORKFLOW_EXECUTION_CAPABILITY,
134
+ );
135
+ if (!workflows) {
136
+ throw new ExpenseServiceError(
137
+ 'WORKFLOWS_UNAVAILABLE',
138
+ 'Workflow execution is not available.',
139
+ 503,
140
+ );
141
+ }
142
+
143
+ const accepted = await workflows.enqueue(
144
+ {
145
+ workflowKey: 'expenses.review',
146
+ input: { claimId: claim.id, amount: claim.amountMinor },
147
+ idempotencyKey: `expense-review:${claim.id}:${claim.version}`,
148
+ },
149
+ {
150
+ tenantId: principal.tenantId,
151
+ actor: userActor({
152
+ accountId: principal.accountId,
153
+ displayName: principal.displayName,
154
+ email: principal.email,
155
+ }),
156
+ origin: {
157
+ kind: 'module',
158
+ moduleId: 'expenses.core',
159
+ operationId: 'expenses.claims.submit',
160
+ },
161
+ permissionSnapshot: [...principal.scopes],
162
+ },
163
+ );
164
+ ```
165
+
166
+ The input does not carry a tenant, actor, scopes, mode, revision, action grants,
167
+ or tool grants. Those values come from trusted server context and the published
168
+ workflow revision.
169
+
170
+ ### An agent invokes a module endpoint that starts a workflow
171
+
172
+ The target module registers a normal agent tool for the endpoint. The tool
173
+ passes `agentActor` built from the trusted tool context and uses the same
174
+ workflow capability. The resulting workflow history names both the agent and
175
+ the authorizing agent run.
176
+
177
+ ```ts
178
+ const actor = agentActor({
179
+ runId: toolContext.runId,
180
+ agentId: toolContext.agentId,
181
+ agentName: toolContext.agentName,
182
+ });
183
+
184
+ await workflows.enqueue(request, {
185
+ tenantId: toolContext.tenantId,
186
+ actor,
187
+ origin: {
188
+ kind: 'module',
189
+ moduleId: 'catalog.core',
190
+ operationId: 'catalog.enrichment.start',
191
+ },
192
+ permissionSnapshot: [...toolContext.permissions],
193
+ });
194
+ ```
195
+
196
+ The current `AgentToolContext` exposes the run but not the agent identity. Stage
197
+ 0 adds `agentId` and `agentName` from the immutable child run snapshot. Until
198
+ then a tool cannot truthfully produce the desired agent actor and must not
199
+ substitute the run id as if it were an agent identity.
200
+
201
+ ### Automations triggers a workflow
202
+
203
+ Version one preserves module independence with an optional integration module,
204
+ for example `automations-workflows.integration`. It depends on both modules and
205
+ maps a schedule or signed webhook to the execution capability.
206
+
207
+ ```ts
208
+ await workflows.enqueue(
209
+ {
210
+ workflowKey: target.workflowKey,
211
+ input: triggerPayload,
212
+ idempotencyKey: `automation:${trigger.id}:${slotOrSignature}`,
213
+ },
214
+ {
215
+ tenantId: trigger.tenantId,
216
+ actor: serviceActor({
217
+ serviceId: 'automations.core',
218
+ label: 'Automations',
219
+ configuredBy: trigger.configuredBy,
220
+ }),
221
+ origin: { kind: 'webhook', triggerId: trigger.id },
222
+ permissionSnapshot: trigger.permissionSnapshot,
223
+ },
224
+ );
225
+ ```
226
+
227
+ The schedule or webhook reference stays in `origin`. It is not disguised as a
228
+ user identifier or an anonymous `system` actor.
229
+
230
+ ## Public execution capability
231
+
232
+ The first public surface is experimental in 0.1. It is module-owned and exported
233
+ from `@flowdular/sdk/modules/workflows/server`.
234
+
235
+ ```ts
236
+ export const WORKFLOW_EXECUTION_CAPABILITY = 'workflows.execution.v1';
237
+
238
+ export type JsonPrimitive = string | number | boolean | null;
239
+ export type JsonValue =
240
+ | JsonPrimitive
241
+ | readonly JsonValue[]
242
+ | { readonly [key: string]: JsonValue };
243
+
244
+ export type WorkflowExecutionOrigin =
245
+ | { readonly kind: 'manual' }
246
+ | {
247
+ readonly kind: 'module';
248
+ readonly moduleId: string;
249
+ readonly operationId: string;
250
+ }
251
+ | { readonly kind: 'schedule'; readonly scheduleId: string }
252
+ | { readonly kind: 'webhook'; readonly triggerId: string };
253
+
254
+ export interface WorkflowInvocationContext {
255
+ readonly tenantId: string;
256
+ readonly actor: Actor;
257
+ readonly origin: WorkflowExecutionOrigin;
258
+ readonly permissionSnapshot: readonly string[];
259
+ }
260
+
261
+ export interface WorkflowCapabilityContext {
262
+ readonly tenantId: string;
263
+ readonly actor: Actor;
264
+ readonly permissionSnapshot: readonly string[];
265
+ }
266
+
267
+ export interface WorkflowEnqueueRequest {
268
+ readonly workflowKey: string;
269
+ readonly input: JsonValue;
270
+ readonly idempotencyKey: string;
271
+ }
272
+
273
+ export interface WorkflowRunAccepted {
274
+ readonly runId: string;
275
+ readonly workflowId: string;
276
+ readonly workflowRevision: number;
277
+ readonly status: 'queued';
278
+ readonly created: boolean;
279
+ }
280
+
281
+ export interface WorkflowExecutionCapability {
282
+ listPublished(
283
+ context: WorkflowCapabilityContext,
284
+ ): readonly WorkflowPublishedReference[];
285
+
286
+ enqueue(
287
+ request: WorkflowEnqueueRequest,
288
+ context: WorkflowInvocationContext,
289
+ ): Promise<WorkflowRunAccepted>;
290
+
291
+ getRun(
292
+ runId: string,
293
+ context: WorkflowCapabilityContext,
294
+ ): WorkflowRunSummary | null;
295
+
296
+ cancel(
297
+ runId: string,
298
+ context: WorkflowCapabilityContext,
299
+ ): WorkflowCancellationResult;
300
+ }
301
+ ```
302
+
303
+ ### Capability lifecycle
304
+
305
+ - The workflow composition registers the capability once.
306
+ - A duplicate capability identifier stops platform boot.
307
+ - Calls before every composition has completed are unsupported. Consumers call
308
+ it only from routes, services, tools, or a composition `start` callback.
309
+ - If the module is absent, `get` returns `null`. A consumer must refuse clearly
310
+ or hide its optional workflow feature.
311
+ - `listPublished`, `getRun`, and `cancel` receive trusted actor and permission
312
+ context and enforce `definitions.read`, `runs.read`, and `runs.cancel`
313
+ respectively. A tenant identifier alone is never read authority.
314
+ - Enqueue commits the durable run before resolving.
315
+ - Reusing the same tenant and idempotency key returns the original run with
316
+ `created: false` when the workflow key and input hash match. A mismatch is a
317
+ stable `WORKFLOW_IDEMPOTENCY_CONFLICT` refusal.
318
+ - A capability reference is valid only during the composed platform lifetime.
319
+ Use after teardown is a programmer error and never silently queues work.
320
+ - Re-entrant calls from a workflow action back into workflow execution are
321
+ refused in version one. A future lineage contract may add bounded subflows.
322
+
323
+ ### Capability misuse resistance
324
+
325
+ - `tenantId`, actor, origin, and permissions are a separate trusted context,
326
+ not fields in user input.
327
+ - Every read, list, enqueue, and cancel operation rechecks the permission needed
328
+ for that operation against the trusted snapshot. Resolving the capability is
329
+ not authorization.
330
+ - The caller cannot choose `mode`. Module calls are live. Dry-run and simulation
331
+ use dedicated editor endpoints.
332
+ - The caller cannot choose a draft or historical workflow revision. The server
333
+ snapshots the current published revision atomically at enqueue.
334
+ - The idempotency key is required, bounded, and namespaced by the caller.
335
+ - Definitions cannot add scopes, action grants, or tool grants.
336
+ - Inputs are JSON only, bounded before persistence, and checked against the
337
+ published workflow input schema.
338
+
339
+ ## Required agent and action capabilities
340
+
341
+ The current `agents.run-queue` contract can only list current agents and enqueue
342
+ the current revision. It cannot observe or cancel a child through that public
343
+ surface. `agents.core` stores the current definition and places an executable
344
+ snapshot on each run, but it does not retain reusable historical agent
345
+ definitions. A workflow therefore cannot execute revision 3 after an agent has
346
+ moved to revision 4. `AgentRun.output` is free-form text and the public enqueue
347
+ contract cannot require a structured output schema.
348
+
349
+ The current `AgentTool` contract also has no `contractVersion`, `outputSchema`,
350
+ or idempotency capability metadata, and there is no public direct tool executor.
351
+ That is not enough for a published workflow action.
352
+
353
+ Before live workflow publication is enabled, `agents.core` must add public
354
+ capabilities with these properties:
355
+
356
+ ```ts
357
+ export interface AgentRevisionReference {
358
+ readonly agentId: string;
359
+ readonly revision: number;
360
+ readonly name: string;
361
+ readonly status: 'active' | 'paused' | 'archived';
362
+ }
363
+
364
+ export interface AgentChildCapabilityContext {
365
+ readonly tenantId: string;
366
+ readonly workflowRunId: string;
367
+ readonly actor: Actor;
368
+ readonly permissionSnapshot: readonly string[];
369
+ }
370
+
371
+ export interface AgentRevisionExecutionCapability {
372
+ getRevision(
373
+ agentId: string,
374
+ revision: number,
375
+ context: AgentChildCapabilityContext,
376
+ ): AgentRevisionReference | null;
377
+
378
+ enqueueRevision(
379
+ request: {
380
+ readonly agentId: string;
381
+ readonly revision: number;
382
+ readonly input: string;
383
+ readonly outputContract:
384
+ | { readonly kind: 'text' }
385
+ | {
386
+ readonly kind: 'json-schema';
387
+ readonly name: string;
388
+ readonly schema: Readonly<Record<string, unknown>>;
389
+ };
390
+ readonly idempotencyKey: string;
391
+ },
392
+ context: AgentChildCapabilityContext,
393
+ ): Promise<{ readonly runId: string; readonly created: boolean }>;
394
+
395
+ readEvents(
396
+ runId: string,
397
+ afterSequence: number,
398
+ context: AgentChildCapabilityContext,
399
+ ): readonly AgentExecutionEvent[];
400
+
401
+ getResult(
402
+ runId: string,
403
+ context: AgentChildCapabilityContext,
404
+ ): AgentRunResult | null;
405
+ requestCancel(runId: string, context: AgentChildCapabilityContext): boolean;
406
+ }
407
+ ```
408
+
409
+ An exact revision must remain executable after a newer revision is published.
410
+ `agents.core` owns a new immutable `agent_definition_revisions` store, or an
411
+ equivalent content-addressed executable snapshot store, and exact-revision
412
+ enqueue reads only that store. Existing run snapshots remain evidence and do
413
+ not become a hidden revision catalog.
414
+
415
+ Every exact-revision catalog, observation, result, and cancellation call carries
416
+ the persisted trusted workflow child context. A bare tenant identifier never
417
+ authorizes access to an agent definition or child run.
418
+
419
+ `agents.core` and `@flowdular/sdk/harness` also own structured output support. An
420
+ agent-decision node supplies a JSON Schema output contract to exact-revision
421
+ enqueue and receives parsed, schema-valid JSON. It never branches by parsing or
422
+ guessing from free-form `AgentRun.output`. Publication refuses a decision node
423
+ when its pinned agent model cannot honor the structured output contract.
424
+
425
+ Action nodes reuse module actions already shaped as agent tools instead of
426
+ creating a second parallel business-operation registry. The action descriptor
427
+ must become a versioned shared contract:
428
+
429
+ ```ts
430
+ export interface VersionedActionDescriptor {
431
+ readonly id: string;
432
+ readonly contractVersion: number;
433
+ readonly description: string;
434
+ readonly requiredPermissions: readonly string[];
435
+ readonly inputSchema: Readonly<Record<string, unknown>>;
436
+ readonly outputSchema: Readonly<Record<string, unknown>>;
437
+ readonly timeoutMs: number;
438
+ readonly idempotency: 'required';
439
+ readonly risk: 'read' | 'workspace-write';
440
+ readonly cancellation: 'cooperative' | 'not-supported';
441
+ }
442
+
443
+ export interface ActionCancellationResult {
444
+ readonly actionInvocationId: string;
445
+ readonly state: 'acknowledged' | 'not-acknowledged' | 'not-supported';
446
+ }
447
+
448
+ export interface ActionInvocationAccepted {
449
+ readonly actionInvocationId: string;
450
+ readonly created: boolean;
451
+ }
452
+
453
+ export interface ActionExecutionResult {
454
+ readonly actionInvocationId: string;
455
+ readonly status: 'succeeded' | 'failed' | 'refused' | 'cancelled';
456
+ readonly output?: JsonValue;
457
+ readonly code?: string;
458
+ }
459
+
460
+ export interface AgentActionExecutionCapability {
461
+ listWorkflowActions(): readonly VersionedActionDescriptor[];
462
+ start(
463
+ request: {
464
+ readonly actionId: string;
465
+ readonly contractVersion: number;
466
+ readonly input: JsonValue;
467
+ readonly idempotencyKey: string;
468
+ },
469
+ context: {
470
+ readonly workflowRunId: string;
471
+ readonly nodeRunId: string;
472
+ readonly tenantId: string;
473
+ readonly actor: Actor;
474
+ readonly permissionSnapshot: readonly string[];
475
+ readonly signal: AbortSignal;
476
+ },
477
+ ): Promise<ActionInvocationAccepted>;
478
+ getResult(
479
+ actionInvocationId: string,
480
+ context: AgentChildCapabilityContext,
481
+ ): ActionExecutionResult | null;
482
+ requestCancel(
483
+ actionInvocationId: string,
484
+ context: AgentChildCapabilityContext,
485
+ ): ActionCancellationResult;
486
+ }
487
+ ```
488
+
489
+ The executor validates input, permissions, version, timeout, output, and
490
+ idempotency before and around the module service call. Acceptance returns or
491
+ persists a stable action invocation identifier before effectful work can be
492
+ lost to recovery. The workflow stores the action identifier and safe result
493
+ evidence. An executor that declares cooperative cancellation passes the signal
494
+ to the action and reports acknowledgement. An executor that cannot cancel still
495
+ supports result observation by invocation identifier. The target module remains
496
+ the owner of its business mutation and record history.
497
+
498
+ The underlying agent tool catalog may still contain `external` and
499
+ `destructive` tools. `agents.actions.v1` excludes them from
500
+ `listWorkflowActions` and refuses them by identifier during `invoke`. Version
501
+ one has no approval node, approval receipt, or approver identity in its enqueue
502
+ contract, so confirmation copy in the canvas is not sufficient authority. A
503
+ later spec must define durable human approval before either risk can enter a
504
+ workflow.
505
+
506
+ These additions are backward compatible:
507
+
508
+ - `agents.run-queue` remains unchanged for `automations.core` and existing
509
+ callers;
510
+ - `agents.core` registers a new `agents.run-execution.v2` capability for exact
511
+ revision enqueue, structured output, observation, and cancellation;
512
+ - `AgentTool` gains optional action metadata, so existing agent-only tools keep
513
+ working unchanged;
514
+ - `agents.core` exposes only tools with complete version, input, output, risk,
515
+ and idempotency metadata through a new `agents.actions.v1` capability;
516
+ - `agents.actions.v1` exposes only `read` and `workspace-write` actions to
517
+ workflows and refuses `external` or `destructive` actions even when such a
518
+ tool is available to a direct agent run;
519
+ - `@flowdular/sdk/harness` owns schema validation, timeout, output bounds, and the
520
+ shared invocation guard, while the registering business module owns the
521
+ service operation and idempotent effect;
522
+ - `workflows.core` consumes these capabilities and owns workflow recovery and
523
+ workflow evidence. It does not reach into their repositories.
524
+
525
+ ## Actor and origin model
526
+
527
+ The kernel `Actor` currently supports `user` and `agent`. Scheduled and webhook
528
+ workflow runs also need a truthful actor. Synthetic actor strings such as
529
+ `system` or `schedule:<id>` erase who configured the authority and make record
530
+ history inconsistent.
531
+
532
+ The kernel actor contract should gain a service variant because business
533
+ modules, workflow history, agent tools, and record history all need the same
534
+ meaning:
535
+
536
+ ```ts
537
+ export interface ServiceActor {
538
+ readonly kind: 'service';
539
+ readonly id: string;
540
+ readonly label: string;
541
+ readonly configuredBy: UserActor;
542
+ }
543
+
544
+ export type Actor = UserActor | AgentActor | ServiceActor;
545
+ ```
546
+
547
+ Execution origin stays separate:
548
+
549
+ - manual calls carry the real user actor;
550
+ - module calls carry the user or agent that caused the module operation;
551
+ - schedules carry the `automations.core` service actor plus the user who last
552
+ configured the schedule, with `origin.kind = 'schedule'` and `scheduleId`;
553
+ - webhooks carry the service actor plus configuring user, with
554
+ `origin.kind = 'webhook'` and `triggerId`.
555
+
556
+ `workflows.core` must not define a private actor union. A module action can
557
+ change a business record, and its owner must be able to append the same actor to
558
+ the shared record-history contract. The kernel extension is therefore the
559
+ correct layer.
560
+
561
+ ## Graph document
562
+
563
+ Every draft and published revision stores a versioned graph document. Canvas
564
+ coordinates are retained for editing but excluded from execution ordering.
565
+
566
+ ```ts
567
+ export interface WorkflowGraphV1 {
568
+ readonly schemaVersion: 1;
569
+ readonly nodes: readonly WorkflowNodeV1[];
570
+ readonly edges: readonly WorkflowEdgeV1[];
571
+ readonly schemas: Readonly<Record<string, JsonSchemaV1>>;
572
+ readonly layout: Readonly<
573
+ Record<string, { readonly x: number; readonly y: number }>
574
+ >;
575
+ }
576
+
577
+ export interface WorkflowEdgeV1 {
578
+ readonly id: string;
579
+ readonly source: { readonly nodeId: string; readonly port: string };
580
+ readonly target: { readonly nodeId: string; readonly port: string };
581
+ readonly label?: string;
582
+ }
583
+
584
+ export type WorkflowNodeV1 =
585
+ | WorkflowInputNodeV1
586
+ | WorkflowAgentNodeV1
587
+ | WorkflowAgentDecisionNodeV1
588
+ | WorkflowGateNodeV1
589
+ | WorkflowValidatorNodeV1
590
+ | WorkflowActionNodeV1
591
+ | WorkflowMergeNodeV1
592
+ | WorkflowOutputNodeV1;
593
+ ```
594
+
595
+ Identifiers are lowercase dot-separated values and remain stable within one
596
+ workflow identity. A copied node receives a new identifier. Renaming a label
597
+ does not change its identifier.
598
+
599
+ ### Ports
600
+
601
+ Each node type owns fixed semantic ports. Every port names a schema from the
602
+ graph schema map.
603
+
604
+ | Node type | Input ports | Output ports | Purpose |
605
+ | ---------------- | ----------- | ------------------------- | ---------------------------------------------------------------------------------- |
606
+ | `input` | none | `data` | Validate and emit invocation input. Exactly one per graph. |
607
+ | `agent` | `input` | `success`, `failure` | Execute one pinned agent revision and expose structured result or bounded failure. |
608
+ | `agent-decision` | `input` | `pass`, `fail`, `failure` | Execute a pinned agent revision whose response must match a pass or fail schema. |
609
+ | `gate` | `input` | `pass`, `fail` | Evaluate deterministic allowlisted logic and forward the unchanged input. |
610
+ | `validator` | `input` | `pass`, `fail` | Validate against a pinned graph schema and emit data or path-addressed errors. |
611
+ | `action` | `input` | `success`, `failure` | Invoke one pinned action contract with a stable idempotency key. |
612
+ | `merge` | `items` | `data` | Collect all reachable incoming envelopes in edge identifier order. |
613
+ | `output` | `input` | none | Validate one terminal workflow result. At least one per graph. |
614
+
615
+ An output port may fan out to several edges. A normal input port accepts one
616
+ edge. The merge `items` port accepts several. Version one merge mode is `all`.
617
+ It waits until every reachable incoming edge emitted or closed, then emits the
618
+ received envelopes in stable edge identifier order. There is no timing-based
619
+ `first` or `race` mode.
620
+
621
+ ### Node references
622
+
623
+ Published nodes contain immutable references:
624
+
625
+ ```ts
626
+ export interface PinnedAgentReference {
627
+ readonly agentId: string;
628
+ readonly revision: number;
629
+ }
630
+
631
+ export interface PinnedActionReference {
632
+ readonly actionId: string;
633
+ readonly contractVersion: number;
634
+ }
635
+ ```
636
+
637
+ Agent publication resolves and pins the exact revision. Action publication
638
+ resolves and pins the exact contract version. A live preflight verifies both
639
+ still exist. It never substitutes the latest agent or action.
640
+
641
+ ### Data mappings
642
+
643
+ Mappings are data, not code. Each target field uses one of three bindings:
644
+
645
+ ```ts
646
+ export type WorkflowBindingV1 =
647
+ | { readonly kind: 'literal'; readonly value: JsonValue }
648
+ | {
649
+ readonly kind: 'path';
650
+ readonly sourceNodeId: string;
651
+ readonly sourcePort: string;
652
+ readonly pointer: string;
653
+ }
654
+ | {
655
+ readonly kind: 'template';
656
+ readonly template: string;
657
+ readonly variables: readonly WorkflowTemplateVariableV1[];
658
+ };
659
+
660
+ export interface WorkflowTargetMappingV1 {
661
+ readonly targetPointer: string;
662
+ readonly binding: WorkflowBindingV1;
663
+ }
664
+ ```
665
+
666
+ - A literal is immutable JSON.
667
+ - A path is an RFC 6901 JSON Pointer into an upstream port envelope.
668
+ - A template uses the existing single-pass `{{ variable }}` contract and
669
+ always produces a string. Its variables are explicit path bindings or
670
+ permission-filtered platform variable definitions.
671
+ - A value emitted by a template is not rescanned for more tokens.
672
+ - There is no JavaScript, `eval`, function body, expression language inside a
673
+ mapping, implicit environment lookup, or property access outside a pointer.
674
+
675
+ The compiler catches obvious source and target schema incompatibility. Runtime
676
+ validation remains authoritative because general JSON Schema assignability is
677
+ not guaranteed to be decidable by the editor.
678
+
679
+ ### Gate logic
680
+
681
+ Gate expressions use a versioned allowlist with literal values, JSON Pointer
682
+ reads, `and`, `or`, `not`, equality, ordered numeric comparison, membership,
683
+ and existence. Missing paths produce a stable gate error, not JavaScript-like
684
+ truthiness. Strings never coerce to numbers or booleans.
685
+
686
+ An agent-based judgment is never embedded in this language. It uses an
687
+ `agent-decision` node whose output schema is:
688
+
689
+ ```ts
690
+ interface AgentDecisionResult {
691
+ readonly decision: 'pass' | 'fail';
692
+ readonly data: JsonValue;
693
+ readonly reason?: string;
694
+ }
695
+ ```
696
+
697
+ Free-form output, another decision string, or schema-invalid data enters the
698
+ node failure policy. The engine does not guess a branch from prose.
699
+
700
+ ## Graph validation and publication
701
+
702
+ Validation is pure and runs in `O(V + E)` time and space. It reports stable
703
+ issues addressed by node, edge, port, mapping, or schema identifier.
704
+
705
+ Publication requires all of the following:
706
+
707
+ 1. Exactly one input node and at least one output node.
708
+ 2. Unique node, edge, port, and schema identifiers.
709
+ 3. No cycle.
710
+ 4. Every node is reachable from input.
711
+ 5. Every reachable terminal path reaches an output or an explicit handled
712
+ failure output.
713
+ 6. Every edge connects an existing compatible output and input port.
714
+ 7. Every required input has the allowed number of incoming edges.
715
+ 8. Every path mapping reads an upstream node, never a future or unrelated node.
716
+ 9. Every template variable exists and is allowed by the publishing principal.
717
+ 10. Every gate operation belongs to logic language version one.
718
+ 11. Every schema belongs to the supported JSON Schema subset and stays within
719
+ depth and size limits.
720
+ 12. Every agent reference resolves to an exact retained active revision.
721
+ 13. Every action resolves to an exact contract version, requires idempotency,
722
+ and has `read` or `workspace-write` risk.
723
+ 14. The graph and its compiled plan stay within all limits.
724
+
725
+ The canonical semantic graph is serialized with stable key order and hashed.
726
+ Layout may be changed in a new draft without changing execution meaning, but a
727
+ published revision stores both the semantic checksum and its layout snapshot.
728
+
729
+ ## Deterministic execution semantics
730
+
731
+ ### Compiled plan
732
+
733
+ Publication compiles a stable topological order. Node identifier is the final
734
+ tie breaker. The compiled plan and compiler version are stored with the
735
+ published revision.
736
+
737
+ Version one runs one ready node at a time. It may gain parallel execution in a
738
+ future engine version, but consumers cannot rely on current wall-clock overlap.
739
+
740
+ ### Edge state
741
+
742
+ When a node settles, each outgoing edge becomes one of:
743
+
744
+ - `emitted`, with schema id, payload hash, byte size, safe preview, source
745
+ attempt, outcome port, and `settledAt`;
746
+ - `closed`, because the source chose another outcome port, with the selected
747
+ port, stable close reason, source attempt, and `settledAt`;
748
+ - `skipped`, because the source node was unreachable or cancelled, with a
749
+ stable skip reason and `settledAt`.
750
+
751
+ Every edge has one immutable settlement. The settlement record names source
752
+ node, source attempt when one existed, target node and target port. Empty or
753
+ retained-away data is represented by typed evidence state rather than by moving
754
+ or omitting the edge row.
755
+
756
+ A downstream node becomes ready when every required incoming edge has emitted,
757
+ or when its merge semantics prove the remaining edges closed. If a required
758
+ edge closes, that node is skipped and its outgoing edges close recursively.
759
+
760
+ Each node runs at most once successfully. Retry attempts do not emit edge data
761
+ until one attempt succeeds or the retry policy settles to failure.
762
+
763
+ ### Node failure policy
764
+
765
+ Every executable node declares one policy:
766
+
767
+ ```ts
768
+ interface WorkflowNodeFailurePolicyV1 {
769
+ readonly maxAttempts: number;
770
+ readonly retryOn: readonly string[];
771
+ readonly backoff: {
772
+ readonly kind: 'fixed' | 'exponential';
773
+ readonly initialMs: number;
774
+ readonly maximumMs: number;
775
+ };
776
+ readonly onExhausted: 'emit-failure' | 'fail-run';
777
+ }
778
+ ```
779
+
780
+ Bounds are part of the graph schema. Permanent refusals, permission failures,
781
+ schema failures, missing versions, and idempotency conflicts are never
782
+ retryable.
783
+
784
+ Agent and action idempotency keys derive from tenant, workflow run, node,
785
+ published revision, and the semantic attempt group. Recovery reuses the same
786
+ key. A retry of a provider failure may create a new agent attempt only when the
787
+ agents capability confirms the prior idempotent enqueue reached a terminal
788
+ retryable result.
789
+
790
+ ### Durable retry schedule
791
+
792
+ The attempt that fails records a stable error code and one classification:
793
+ `retryable` or `permanent`. When policy allows another attempt, the same
794
+ transaction appends `node.retry.scheduled` with:
795
+
796
+ - node id and completed attempt number;
797
+ - semantic attempt group id and unchanged side-effect idempotency key;
798
+ - matched `retryOn` code and retry classification;
799
+ - selected backoff in milliseconds;
800
+ - absolute `nextAttemptAt` for live execution;
801
+ - virtual next-attempt offset for simulation.
802
+
803
+ The node projection becomes `waiting-retry`. A worker starts the next immutable
804
+ attempt only after the stored time and appends `node.retry.started`. Recovery
805
+ uses the recorded time and delay. It never recomputes jitter or backoff. Version
806
+ one applies no random jitter, which keeps replay and recovery deterministic.
807
+ Permanent failures and refusals never produce `node.retry.scheduled`.
808
+
809
+ ## Execution modes
810
+
811
+ ### Dry-run
812
+
813
+ Dry-run accepts a draft graph and sample input. It validates, resolves
814
+ references, checks the caller's current permissions, compiles the plan, and
815
+ returns issues plus the plan summary.
816
+
817
+ ```ts
818
+ export interface WorkflowValidationIssueV1 {
819
+ readonly code: string;
820
+ readonly severity: 'error' | 'warning';
821
+ readonly message: string;
822
+ readonly location:
823
+ | { readonly kind: 'graph' }
824
+ | { readonly kind: 'node'; readonly nodeId: string; readonly path?: string }
825
+ | {
826
+ readonly kind: 'edge';
827
+ readonly edgeId: string;
828
+ readonly path?: string;
829
+ };
830
+ }
831
+
832
+ export interface WorkflowDryRunResponseV1 {
833
+ readonly reportVersion: 1;
834
+ readonly graphChecksum: string;
835
+ readonly valid: boolean;
836
+ readonly issues: readonly WorkflowValidationIssueV1[];
837
+ readonly compiledOrder: readonly string[];
838
+ readonly references: readonly {
839
+ readonly kind: 'agent' | 'action' | 'schema';
840
+ readonly id: string;
841
+ readonly version: string;
842
+ readonly available: boolean;
843
+ }[];
844
+ readonly requiredPermissions: readonly string[];
845
+ readonly limits: Readonly<Record<string, number>>;
846
+ }
847
+ ```
848
+
849
+ It guarantees:
850
+
851
+ - no workflow run row;
852
+ - no run id, invocation history, node attempt, edge transfer, run event,
853
+ payload row, usage rollup, cost rollup, or workflow audit evidence;
854
+ - no provider or agent run;
855
+ - no registered action call;
856
+ - no business data write;
857
+ - no audit event other than normal request security logging;
858
+ - no secret resolution.
859
+
860
+ ### Simulation
861
+
862
+ Simulation accepts a draft or published graph, sample input, and bounded node
863
+ fixtures. Agent, agent-decision, and action nodes require fixtures. Gate,
864
+ validator, merge, input, output, and mappings execute for real against fixture
865
+ data.
866
+
867
+ Each fixture may declare `simulatedDurationMs`. The engine creates virtual
868
+ timestamps and a deterministic event plan. It does not sleep. The client may
869
+ animate the plan at a selected playback speed.
870
+
871
+ Simulation persists a run marked `simulate` so it appears in history with its
872
+ actor, revision or draft checksum, node data, and event path. It never calls a
873
+ provider, action, outbound network, or business repository.
874
+
875
+ Each simulation event stores the wall-clock `recordedAt` at which evidence was
876
+ persisted plus `virtualOffsetMs` from the simulation start. It has no fabricated
877
+ wall-clock `occurredAt`. Ordering is still the durable run sequence. Simulation
878
+ usage and cost rollups use `state: 'not-applicable'`, zero counters, no pricing
879
+ snapshot, and no child or action correlation. Fixture provenance is retained as
880
+ a safe fixture hash, never as provider usage.
881
+
882
+ ### Live
883
+
884
+ Live mode accepts only a published revision. It performs a fresh preflight,
885
+ persists the run, and may call exact agent revisions plus registered `read` or
886
+ `workspace-write` actions.
887
+
888
+ The canvas labels this action `Run live`, not `Test`, because it may produce
889
+ real provider cost and business side effects. External and destructive actions
890
+ are unavailable in version one because the workflow contract has no durable
891
+ human approval receipt.
892
+
893
+ ## Durable execution and recovery
894
+
895
+ A live invocation commits before returning:
896
+
897
+ - workflow run id, tenant, workflow id, key, and published revision;
898
+ - semantic graph checksum and compiler version;
899
+ - actor and separate origin;
900
+ - permission snapshot and digest;
901
+ - mode, input hash, safe input reference, limits, and idempotency key;
902
+ - `queued` status and first ordered event.
903
+
904
+ The workflow worker claims a run with an expiring lease. It renews the lease
905
+ while it owns the run. Node intent is committed before a child agent or action
906
+ is called. Child id and idempotency key are committed as soon as the public
907
+ capability returns.
908
+
909
+ After a crash, a new worker reads the last node attempt:
910
+
911
+ - if no call was accepted, it repeats the call with the same key;
912
+ - if an agent child id exists, it observes that exact run;
913
+ - if an action accepted the key, it reads or repeats the same idempotent result;
914
+ - if evidence is inconsistent, it refuses recovery with
915
+ `WORKFLOW_RECOVERY_INCONSISTENT` and never guesses.
916
+
917
+ The browser does not hold a lease and cannot stop recovery by disconnecting.
918
+
919
+ ## Status model
920
+
921
+ ### Event envelope and catalog
922
+
923
+ The append-only stream is the source of truth. Every durable event uses this
924
+ envelope and a payload schema fixed by `schemaVersion` plus `type`:
925
+
926
+ ```ts
927
+ export type WorkflowRunEventTypeV1 =
928
+ | 'run.queued'
929
+ | 'run.claimed'
930
+ | 'run.recovered'
931
+ | 'node.ready'
932
+ | 'node.attempt.started'
933
+ | 'node.child.waiting'
934
+ | 'node.attempt.settled'
935
+ | 'node.retry.scheduled'
936
+ | 'node.retry.started'
937
+ | 'node.skipped'
938
+ | 'edge.settled'
939
+ | 'run.cancel.requested'
940
+ | 'node.cancel.requested'
941
+ | 'node.cancel.acknowledged'
942
+ | 'node.cancel.not-acknowledged'
943
+ | 'node.result.late-ignored'
944
+ | 'payload.retention.applied'
945
+ | 'run.succeeded'
946
+ | 'run.failed'
947
+ | 'run.refused'
948
+ | 'run.cancelled';
949
+
950
+ export interface WorkflowRunEventV1 {
951
+ readonly eventId: string;
952
+ readonly schemaVersion: 1;
953
+ readonly tenantId: string;
954
+ readonly runId: string;
955
+ readonly sequence: number;
956
+ readonly type: WorkflowRunEventTypeV1;
957
+ readonly recordedAt: number;
958
+ readonly virtualOffsetMs?: number;
959
+ readonly payload: Readonly<Record<string, JsonValue>>;
960
+ }
961
+ ```
962
+
963
+ `sequence` starts at one and is contiguous within one tenant and run.
964
+ `recordedAt` is the evidence persistence time. Only simulation events carry
965
+ `virtualOffsetMs`. Unknown schema versions or event types stop projection repair
966
+ with `WORKFLOW_EVENT_SCHEMA_UNSUPPORTED`; they are never skipped or guessed.
967
+
968
+ | Event type | Required payload | Projection effect |
969
+ | ------------------------------ | ------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------- |
970
+ | `run.queued` | workflow revision or draft checksum, actor, origin, mode | Create `queued` run. |
971
+ | `run.claimed` | worker id, lease expiry | `queued` or recovered wait becomes `running`. |
972
+ | `run.recovered` | prior lease, worker id, recovery reason | Keep legal non-terminal state and record new ownership. |
973
+ | `node.ready` | node id | Node becomes `ready`; run stays or becomes `running`. |
974
+ | `node.attempt.started` | node id, attempt, semantic group, input evidence | Append one `running` attempt. |
975
+ | `node.child.waiting` | node id, attempt, child kind and correlation id | Attempt becomes `waiting-child`; run becomes `waiting-agent` only for an agent child. |
976
+ | `node.attempt.settled` | node id, attempt, technical status, outcome port, evidence, error classification | Make that attempt terminal and project the node result. |
977
+ | `node.retry.scheduled` | node id, prior attempt, classification, backoff, next attempt time | Node and run become `waiting-retry`. |
978
+ | `node.retry.started` | node id, next attempt, scheduled event sequence | Return node and run to `running` before the next attempt starts. |
979
+ | `node.skipped` | node id, reason | Node becomes terminal `skipped` without creating an attempt. |
980
+ | `edge.settled` | edge id, emitted, closed, or skipped state, source attempt, target, reason, evidence | Append one immutable edge settlement without changing run status. |
981
+ | `run.cancel.requested` | requester, reason, requested time | Run becomes `cancel-requested` and no new node may start. |
982
+ | `node.cancel.requested` | node id, attempt, child kind and correlation | Record cooperative request without changing attempt terminal state. |
983
+ | `node.cancel.acknowledged` | node id, attempt, child kind and correlation | Record acknowledgement while the worker still observes terminal settlement. |
984
+ | `node.cancel.not-acknowledged` | node id, attempt, child kind, reason | Record rejection, timeout, or unsupported cancellation. |
985
+ | `node.result.late-ignored` | node id, attempt, child correlation, result hash and terminal status | Retain safe evidence but never emit an edge after cancellation. |
986
+ | `payload.retention.applied` | payload id, hash, policy and prior evidence state | Project its evidence state to `expired`. |
987
+ | `run.succeeded` | output evidence and final rollups | Terminal `succeeded`. |
988
+ | `run.failed` | stable error and final rollups | Terminal `failed`. |
989
+ | `run.refused` | stable refusal and final rollups | Terminal `refused`. |
990
+ | `run.cancelled` | acknowledgement summary and final rollups | Terminal `cancelled`. |
991
+
992
+ ### Workflow run projection
993
+
994
+ Intermediate statuses are `queued`, `running`, `waiting-agent`,
995
+ `waiting-retry`, and `cancel-requested`. Terminal statuses are `succeeded`,
996
+ `failed`, `refused`, and `cancelled`.
997
+
998
+ Legal transitions are:
999
+
1000
+ - `queued` to `running`, `cancel-requested`, or `refused`;
1001
+ - `running`, `waiting-agent`, and `waiting-retry` may move among each other as
1002
+ catalog events require, or move to `cancel-requested`, `succeeded`, `failed`,
1003
+ or `refused`;
1004
+ - `cancel-requested` moves only to `cancelled` after in-flight work has been
1005
+ observed or bounded by its timeout;
1006
+ - every terminal status is immutable.
1007
+
1008
+ `run.recovered` never widens these transitions. An illegal event transition
1009
+ stops the worker and projection repair with
1010
+ `WORKFLOW_EVENT_TRANSITION_INVALID`. Projection rows are caches that can be
1011
+ rebuilt from sequence one without inventing an event.
1012
+
1013
+ ### Node and attempt projection
1014
+
1015
+ A node execution projection has status `pending`, `ready`, `running`,
1016
+ `waiting-child`, `waiting-retry`, `succeeded`, `failed`, `refused`, `skipped`,
1017
+ or `cancelled`. Pending, ready, waiting-retry, and skipped are node states, not
1018
+ attempt records.
1019
+
1020
+ An immutable attempt exists only after `node.attempt.started`. Its technical
1021
+ status is `running`, `waiting-child`, `succeeded`, `failed`, `refused`, or
1022
+ `cancelled`. A terminal attempt never changes. `pass` and `fail` are normal,
1023
+ schema-bound outcome-port identifiers of a technically `succeeded` decision,
1024
+ gate, or validator attempt. They are never attempt statuses and a fail outcome
1025
+ does not by itself fail the run.
1026
+
1027
+ Every attempt records start, completion, duration, outcome port, retry
1028
+ classification and decision, failure or refusal code, safe input and output
1029
+ evidence, child run or action correlation, usage, cost, and event sequence
1030
+ bounds. A retry appends a new attempt number. Cancellation or an unreachable
1031
+ branch may terminate a node without fabricating an attempt.
1032
+
1033
+ ## Full run history and evidence
1034
+
1035
+ Run history is tenant-scoped and cursor-paginated. It supports filters for:
1036
+
1037
+ - workflow id or key;
1038
+ - exact workflow revision;
1039
+ - actor kind and actor id;
1040
+ - origin kind and origin reference;
1041
+ - mode;
1042
+ - intermediate or terminal status;
1043
+ - queued and completed time range;
1044
+ - child agent id;
1045
+ - failure or refusal code.
1046
+
1047
+ Interactive ordering is fixed to `(queuedAt DESC, runId DESC)`. The first page
1048
+ captures a high-water mark. Its opaque cursor has version `wfrc1` and is signed
1049
+ by the server over tenant id, a canonical filter digest, the fixed sort, the
1050
+ high-water mark, and the last `(queuedAt, runId)` pair. Later pages use the same
1051
+ snapshot boundary, so newly queued runs do not shift or duplicate existing
1052
+ rows. A cursor is valid only for the authenticated tenant and the exact filter
1053
+ set that created it. Malformed, modified, foreign-tenant, stale-version, or
1054
+ filter-mismatched cursors return `WORKFLOW_CURSOR_INVALID` or
1055
+ `WORKFLOW_CURSOR_MISMATCH` and no rows.
1056
+
1057
+ Workflow audit pages use the same rule with `(sequence DESC)` and cursor version
1058
+ `wfac1`. Run detail is not cursor-paged because contract limits bound it to at
1059
+ most 100 nodes, 500 immutable attempts, and 200 edge settlements. The event
1060
+ stream remains separately paged because it may contain 10,000 events.
1061
+
1062
+ The list row includes workflow, revision, actor, origin, mode, current status,
1063
+ queued time, elapsed or final duration, node progress, child usage, priced cost,
1064
+ unpriced child and action counts, and terminal failure summary.
1065
+
1066
+ Run detail includes:
1067
+
1068
+ - immutable invocation snapshot;
1069
+ - published graph and compiled order;
1070
+ - every node and attempt with status and duration;
1071
+ - every edge emission, closure, and skip;
1072
+ - safe redacted input and output previews;
1073
+ - payload hashes, schemas, and byte sizes;
1074
+ - child agent run ids and action invocation ids;
1075
+ - aggregate usage and cost;
1076
+ - cancellation request and acknowledgement;
1077
+ - workflow audit event ids and target-module history correlation ids.
1078
+
1079
+ Each payload field uses `WorkflowPayloadEvidenceV1`, so an absent value, a JSON
1080
+ `null`, a redacted value, and an expired value cannot be confused.
1081
+
1082
+ Provider credentials, session tokens, hidden reasoning, raw secret variables,
1083
+ and unrestricted request bodies are never history data.
1084
+
1085
+ ### Event stream and resume
1086
+
1087
+ Every run event has a run-local sequence and durable event id. SSE `id` is an
1088
+ opaque run-bound resume cursor `wfre1` signed over tenant, run, and sequence. It
1089
+ is not the database event id. The event data still includes `eventId`,
1090
+ `schemaVersion`, and `sequence`.
1091
+
1092
+ A client reconnects with either HTTP `Last-Event-ID: <wfre1 cursor>` or an
1093
+ integer `afterSequence`. If both are supplied they must name the same run and
1094
+ sequence or the server returns `WORKFLOW_EVENT_CURSOR_CONFLICT`. A cursor for a
1095
+ different tenant or run returns `WORKFLOW_EVENT_CURSOR_INVALID`. A sequence
1096
+ beyond the durable tail returns `WORKFLOW_EVENT_CURSOR_AHEAD`.
1097
+
1098
+ The server replays at most 100 persisted events per connection. When more
1099
+ remain, it emits a non-durable `workflow.replay-boundary` control event with
1100
+ `nextAfterSequence` and `hasMore: true`, then closes. The client reconnects from
1101
+ that sequence. Once caught up, persisted live events continue in order.
1102
+ Heartbeat comments are emitted at most every 15 seconds, carry no SSE id, and
1103
+ never advance a sequence. After a terminal run event is flushed, the server
1104
+ emits a non-durable `workflow.stream-complete` control event naming the terminal
1105
+ sequence and closes normally.
1106
+
1107
+ Reloading, filtering away the run, or closing the tab does not alter it. A
1108
+ subscriber that falls behind receives a bounded replay page and reconnect
1109
+ cursor instead of forcing the server to buffer without limit.
1110
+
1111
+ ### Cancellation
1112
+
1113
+ Cancellation appends one idempotent `cancel-requested` event. The worker stops
1114
+ scheduling new nodes and appends `node.cancel.requested` for each in-flight
1115
+ child agent or action before calling its public cancellation capability.
1116
+
1117
+ An agent or cooperative action records `node.cancel.acknowledged` when it
1118
+ accepts the request. Rejection, timeout, and `not-supported` record
1119
+ `node.cancel.not-acknowledged` with a stable reason. In every case the worker
1120
+ observes accepted work until terminal or until its already persisted timeout
1121
+ settles it. An action that committed its idempotent effect before cancellation
1122
+ keeps that target-module history. The workflow never claims compensation.
1123
+
1124
+ A child or action result arriving after `run.cancel.requested` is recorded as
1125
+ `node.result.late-ignored` with correlation id, terminal status, output hash,
1126
+ and redacted evidence state. Its output is never emitted to an edge and cannot
1127
+ start another node. Once all in-flight work is terminal or timed out, the run
1128
+ appends `run.cancelled`. It never transitions from cancel-requested to failed or
1129
+ succeeded.
1130
+
1131
+ Calling cancel again returns the existing cancellation state. Cancelling a
1132
+ terminal run changes nothing and returns its terminal status.
1133
+
1134
+ ### Payload execution and evidence
1135
+
1136
+ Execution data and history evidence are different records:
1137
+
1138
+ - an execution payload is bounded JSON encrypted with authenticated encryption,
1139
+ a server-managed key id, and the shortest retention required for execution or
1140
+ recovery. It is available only to the owning worker and never returned by a
1141
+ history, event, stream, or audit API;
1142
+ - an evidence preview is produced through scope filtering and redaction before
1143
+ persistence. It may contain bounded safe JSON and is the only payload shape
1144
+ exposed to readers.
1145
+
1146
+ ```ts
1147
+ export interface WorkflowPayloadEvidenceV1 {
1148
+ readonly version: 1;
1149
+ readonly state: 'available' | 'redacted' | 'truncated' | 'expired' | 'absent';
1150
+ readonly schemaId: string;
1151
+ readonly hash: string;
1152
+ readonly originalByteSize: number;
1153
+ readonly preview?: JsonValue;
1154
+ readonly reason?:
1155
+ | 'secret'
1156
+ | 'scope-denied'
1157
+ | 'size-limit'
1158
+ | 'retention'
1159
+ | 'not-emitted';
1160
+ }
1161
+ ```
1162
+
1163
+ `available` with `preview: null` is a real JSON null. `absent` means no payload
1164
+ was emitted. `redacted` and `truncated` retain hash, schema, and original size.
1165
+ Retention changes only the preview state to `expired`; it never rewrites hashes
1166
+ or pretends the prior value was null. A secret field may exist briefly only in
1167
+ the encrypted execution payload. It never enters evidence, run events, SSE, or
1168
+ audit metadata.
1169
+
1170
+ ### Retention
1171
+
1172
+ Core evidence tables are append-only. Automated retention applies only to
1173
+ separate payload blobs. Before a blob expires, the worker appends a
1174
+ `payload-retention-applied` event with its hash and policy. It then deletes the
1175
+ blob while keeping the run, status transitions, revisions, actor, origin,
1176
+ attempts, edge hashes, durations, usage, cost, failures, and audit linkage.
1177
+
1178
+ The default metadata retention is indefinite in version one. A future deletion
1179
+ policy requires a separate approved spec because removing audit evidence is a
1180
+ compliance decision, not storage cleanup.
1181
+
1182
+ ## Authorization and audit
1183
+
1184
+ Permissions are:
1185
+
1186
+ - `workflows.definitions.read`
1187
+ - `workflows.definitions.manage`
1188
+ - `workflows.definitions.publish`
1189
+ - `workflows.runs.read`
1190
+ - `workflows.runs.execute`
1191
+ - `workflows.runs.cancel`
1192
+
1193
+ Definition permissions do not imply action permissions. Publishing verifies
1194
+ that the publisher can inspect every referenced agent and action. Live
1195
+ execution checks the workflow permission and intersects each node with the
1196
+ trusted permission snapshot captured at enqueue.
1197
+
1198
+ A workflow cannot grant a scope, tool, agent, or action. A node cannot accept a
1199
+ tenant or permission list from graph data.
1200
+
1201
+ The workflow audit chain records:
1202
+
1203
+ - definition create, draft save, publish, archive, and eligible
1204
+ delete;
1205
+ - run enqueue, claim, recover, settle, refuse, and cancel;
1206
+ - node start, retry, child correlation, action correlation, settle, and skip;
1207
+ - payload retention;
1208
+ - actor, separate origin, subject, safe metadata, previous hash, and event hash.
1209
+
1210
+ The following transitions are `audit-required` and commit in one database
1211
+ transaction with their projection update, ordered run event where a run exists,
1212
+ and hash-chain event:
1213
+
1214
+ - definition create, draft save, publish, archive, and eligible delete;
1215
+ - run enqueue, live or simulation refusal, claim after enqueue, recovery,
1216
+ cancellation request, and terminal settlement;
1217
+ - node attempt start, retry schedule, child or action correlation, terminal
1218
+ attempt settlement, and node skip;
1219
+ - payload retention before deletion.
1220
+
1221
+ Lease renewal, heartbeat, safe payload read, and edge-only settlement do not
1222
+ enter the tenant hash chain. Edge settlements remain immutable ordered run
1223
+ events and are correlated through run and source attempt. This boundary avoids
1224
+ claiming that high-volume data movement is an administrative action while still
1225
+ making it reconstructable.
1226
+
1227
+ If the audit append or hash update fails, the projection and ordered run event
1228
+ in that transaction roll back. Cross-module child audit and target-module record
1229
+ history cannot share a database transaction; the workflow atomically commits
1230
+ their stable correlation identifiers and each owner keeps its own audit
1231
+ boundary.
1232
+
1233
+ Audit pagination uses tenant sequence descending and the `wfac1` cursor. Verify
1234
+ returns a typed result with `valid`, `checkedThroughSequence`, and, when broken,
1235
+ `firstBrokenSequence`, `expectedPreviousHash`, and `actualPreviousHash`. It never
1236
+ returns secret metadata.
1237
+
1238
+ Child agent details remain in agents audit and run history. Business mutation
1239
+ details remain in the target module history. Workflow events store correlation
1240
+ ids rather than copying their private evidence.
1241
+
1242
+ ## Cost accounting
1243
+
1244
+ The workflow aggregates child agent usage and cost by child run id. It counts
1245
+ each terminal child once, including retried nodes that created distinct child
1246
+ runs. It separates priced cost from unpriced usage.
1247
+
1248
+ An action has no inferred price. A versioned action may return an explicit
1249
+ metering record in a future contract, but version one stores only action
1250
+ duration and outcome. Workflow cost is therefore child agent cost plus an
1251
+ `unpricedActions` count, not a guessed total.
1252
+
1253
+ ```ts
1254
+ export interface WorkflowUsageRollupV1 {
1255
+ readonly version: 1;
1256
+ readonly state: 'not-applicable' | 'provisional' | 'final';
1257
+ readonly inputTokens: number;
1258
+ readonly outputTokens: number;
1259
+ readonly totalTokens: number;
1260
+ readonly includedChildRunIds: readonly string[];
1261
+ readonly pricedChildRuns: number;
1262
+ readonly unpricedChildRuns: number;
1263
+ readonly actionInvocations: number;
1264
+ readonly unpricedActions: number;
1265
+ }
1266
+
1267
+ export interface WorkflowCostRollupV1 {
1268
+ readonly version: 1;
1269
+ readonly state: 'not-applicable' | 'provisional' | 'final';
1270
+ readonly currency: 'USD';
1271
+ readonly amountMicros: number;
1272
+ readonly pricingSnapshotIds: readonly string[];
1273
+ readonly unpricedChildRuns: number;
1274
+ readonly unpricedActions: number;
1275
+ }
1276
+ ```
1277
+
1278
+ All counters and micro-USD amounts are non-negative safe integers. One USD is
1279
+ 1,000,000 micros. A child enters the aggregate once by child run id after its
1280
+ own terminal usage record is available. The workflow stores that child's
1281
+ pricing snapshot identifier and never reprices historical usage. A live rollup
1282
+ is `provisional` while any included child is unsettled and `final` at workflow
1283
+ terminal settlement. Simulation uses `not-applicable`, zero counters and amount,
1284
+ and no pricing snapshot. Dry-run creates no rollup. Actions are counted only as
1285
+ `unpricedActions` in version one.
1286
+
1287
+ ## API endpoints
1288
+
1289
+ Every protected endpoint uses the normal auth identity, trusted tenant, CSRF,
1290
+ body bounds, and workflow permission.
1291
+
1292
+ | Method | Path | Permission | Purpose |
1293
+ | ------ | ------------------------------- | ------------------------------- | ----------------------------------------------------------------------- |
1294
+ | GET | `/api/workflows` | `workflows.definitions.read` | List definitions and current draft or published revision. |
1295
+ | POST | `/api/workflows` | `workflows.definitions.manage` | Create a draft. |
1296
+ | GET | `/api/workflows/detail` | `workflows.definitions.read` | Read one definition and revision history. |
1297
+ | POST | `/api/workflows/update` | `workflows.definitions.manage` | Save a draft with expected revision. |
1298
+ | POST | `/api/workflows/validate` | `workflows.runs.execute` | Pure dry-run and compile report. |
1299
+ | POST | `/api/workflows/publish` | `workflows.definitions.publish` | Publish an immutable pinned revision. |
1300
+ | POST | `/api/workflows/archive` | `workflows.definitions.manage` | Prevent new live runs. |
1301
+ | POST | `/api/workflows/delete` | `workflows.definitions.manage` | Delete only an unpublished unused draft. |
1302
+ | GET | `/api/workflow-catalog/agents` | `workflows.definitions.read` | List exact agent revisions visible to the editor. |
1303
+ | GET | `/api/workflow-catalog/actions` | `workflows.definitions.read` | List scoped versioned read and workspace-write actions. |
1304
+ | POST | `/api/workflow-runs/simulate` | `workflows.runs.execute` | Persist a deterministic fixture simulation. |
1305
+ | POST | `/api/workflow-runs` | `workflows.runs.execute` | Enqueue a live published workflow. |
1306
+ | GET | `/api/workflow-runs` | `workflows.runs.read` | Filter and cursor-page run history. |
1307
+ | GET | `/api/workflow-runs/detail` | `workflows.runs.read` | Read run, node attempts, edges, usage, cost, and safe payload evidence. |
1308
+ | GET | `/api/workflow-runs/events` | `workflows.runs.read` | Resume persisted SSE by run and sequence. |
1309
+ | POST | `/api/workflow-runs/cancel` | `workflows.runs.cancel` | Request cooperative cancellation. |
1310
+ | GET | `/api/workflow-audit` | `workflows.runs.read` | Page workflow-local audit evidence. |
1311
+ | GET | `/api/workflow-audit/verify` | `workflows.runs.read` | Verify the tenant hash chain. |
1312
+
1313
+ Stable errors include:
1314
+
1315
+ - `WORKFLOW_NOT_FOUND`
1316
+ - `WORKFLOW_NOT_PUBLISHED`
1317
+ - `WORKFLOW_ARCHIVED`
1318
+ - `WORKFLOW_REVISION_CONFLICT`
1319
+ - `WORKFLOW_GRAPH_INVALID`
1320
+ - `WORKFLOW_AGENT_REVISION_MISSING`
1321
+ - `WORKFLOW_ACTION_VERSION_MISSING`
1322
+ - `WORKFLOW_PERMISSION_DENIED`
1323
+ - `WORKFLOW_INPUT_INVALID`
1324
+ - `WORKFLOW_LIMIT_EXCEEDED`
1325
+ - `WORKFLOW_IDEMPOTENCY_CONFLICT`
1326
+ - `WORKFLOW_RECOVERY_INCONSISTENT`
1327
+ - `WORKFLOW_RUN_TERMINAL`
1328
+ - `WORKFLOW_CURSOR_INVALID`
1329
+ - `WORKFLOW_CURSOR_MISMATCH`
1330
+ - `WORKFLOW_EVENT_CURSOR_INVALID`
1331
+ - `WORKFLOW_EVENT_CURSOR_CONFLICT`
1332
+ - `WORKFLOW_EVENT_CURSOR_AHEAD`
1333
+ - `WORKFLOW_EVENT_SCHEMA_UNSUPPORTED`
1334
+ - `WORKFLOW_EVENT_TRANSITION_INVALID`
1335
+
1336
+ ## Persistence outline
1337
+
1338
+ All tenant-owned indexes start with `tenant_id`. Cross-module identifiers are
1339
+ plain references, never foreign keys.
1340
+
1341
+ ### `workflow_definitions`
1342
+
1343
+ - id, tenant_id, key, name, description;
1344
+ - lifecycle status;
1345
+ - current draft revision and published revision;
1346
+ - optimistic revision, created and updated actor and time.
1347
+
1348
+ ### `workflow_revisions`
1349
+
1350
+ - id, tenant_id, workflow_id, revision;
1351
+ - graph schema version and canonical graph JSON;
1352
+ - semantic checksum, compiler version, compiled plan JSON;
1353
+ - immutable publication actor and time;
1354
+ - unique tenant, workflow, revision.
1355
+
1356
+ Published rows are never updated.
1357
+
1358
+ ### `workflow_runs`
1359
+
1360
+ - id, tenant_id, workflow_id, workflow_revision;
1361
+ - graph checksum, compiler version, mode, status;
1362
+ - actor kind, actor id, actor label, actor run id or service configuring user;
1363
+ - origin kind and origin reference;
1364
+ - permission digest, input hash, payload reference;
1365
+ - idempotency key, limits, lease owner and expiry;
1366
+ - typed usage and cost rollup version, state, integer counters, micro-USD,
1367
+ pricing snapshot references, and unpriced child and action counts;
1368
+ - queued, started, completed, cancellation times;
1369
+ - failure and refusal code.
1370
+
1371
+ Unique tenant and idempotency key provides the enqueue backstop.
1372
+
1373
+ ### `workflow_node_states`
1374
+
1375
+ - tenant_id, run_id, node_id, projected execution status;
1376
+ - latest attempt number, selected outcome port, next_attempt_at;
1377
+ - first ready, started, and settled times;
1378
+ - unique tenant, run, node.
1379
+
1380
+ This table is a rebuildable projection. It never replaces immutable attempts or
1381
+ ordered events.
1382
+
1383
+ ### `workflow_node_attempts`
1384
+
1385
+ - tenant_id, run_id, node_id, attempt;
1386
+ - node type, immutable technical status, separate outcome port;
1387
+ - input and output hash and payload references;
1388
+ - child agent run id or action invocation id;
1389
+ - semantic attempt group and side-effect idempotency key;
1390
+ - failure, refusal, retry classification and decision, selected backoff,
1391
+ next_attempt_at, usage, and cost;
1392
+ - started, completed, duration.
1393
+
1394
+ Unique tenant, run, node, attempt prevents duplicate attempt rows.
1395
+
1396
+ ### `workflow_edge_transfers`
1397
+
1398
+ - tenant_id, run_id, edge_id, source attempt;
1399
+ - source node and outcome port, target node and port;
1400
+ - state, stable settlement reason, schema id, payload hash and reference, byte
1401
+ size;
1402
+ - one settled_at timestamp for emitted, closed, and skipped states.
1403
+
1404
+ ### `workflow_run_events`
1405
+
1406
+ - event_id, schema_version, tenant_id, run_id, sequence, catalog event type;
1407
+ - safe typed payload, recorded_at, optional simulation virtual_offset_ms;
1408
+ - unique tenant, run, sequence;
1409
+ - append-only source for SSE and projection repair.
1410
+
1411
+ ### `workflow_payloads`
1412
+
1413
+ - tenant_id, id, kind `execution` or `evidence`, schema id, hash, original byte
1414
+ size;
1415
+ - encrypted bounded JSON plus encryption key id only for execution kind;
1416
+ - evidence state, bounded safe preview, redaction reason only for evidence kind;
1417
+ - retention policy, expiry, created time.
1418
+
1419
+ Payloads are separate so retention never rewrites core execution evidence.
1420
+ History and event APIs can select evidence rows only. A database check prevents
1421
+ an execution row from carrying a preview and an evidence row from carrying
1422
+ encrypted source data.
1423
+
1424
+ ### `workflow_audit_events`
1425
+
1426
+ - tenant_id, sequence, actor, origin, action, subject, safe metadata;
1427
+ - occurred time, previous hash, event hash.
1428
+
1429
+ The same verifier backs HTTP and a future CLI command.
1430
+
1431
+ ## Canvas behavior
1432
+
1433
+ The editor uses the shared design system. The canvas is one screen with named
1434
+ subcomponents for node palette, canvas, inspector, validation panel, test panel,
1435
+ and execution history.
1436
+
1437
+ ### Editing
1438
+
1439
+ - Dragging changes layout only.
1440
+ - Connecting ports checks direction, cardinality, and obvious schema
1441
+ compatibility immediately.
1442
+ - A cycle is refused as soon as the edge is proposed.
1443
+ - Node forms use registered agent revisions, action versions, schemas, bindings,
1444
+ retry policy, and failure routing. There is no free-form code field.
1445
+ - Autosave uses expected revision and shows a visible conflict instead of last
1446
+ write wins.
1447
+ - Publish shows graph errors and the exact pinned dependency summary.
1448
+ - The editor keeps semantic changes and layout changes distinguishable in the
1449
+ revision review.
1450
+
1451
+ ### Testing
1452
+
1453
+ - `Dry-run` shows the compiled order, references, permissions, mappings, and
1454
+ issues without adding history.
1455
+ - `Simulate` opens a fixture drawer for nondeterministic nodes. Each node can
1456
+ receive safe input, output, decision, failure, and virtual duration fixtures.
1457
+ - Simulation produces the same event shapes as live execution, marked
1458
+ `simulate`.
1459
+ - `Run live` is explicit and displays referenced agents, actions, required
1460
+ permissions, and side-effect risks before enqueue.
1461
+
1462
+ ### Execution overlay
1463
+
1464
+ The canvas reads persisted events and displays:
1465
+
1466
+ - pending nodes with neutral state;
1467
+ - the current node and traversing edge with a restrained animated highlight;
1468
+ - pass and success in success state;
1469
+ - fail branch as a normal decision, not an error;
1470
+ - refused or failed nodes in error state;
1471
+ - closed and skipped branches with reduced emphasis;
1472
+ - retry attempt and backoff on the node;
1473
+ - safe input and output in the inspector;
1474
+ - total duration, child agent usage, and cost in the run header.
1475
+
1476
+ Animation is a projection of event evidence. It never drives execution.
1477
+ Reopening a run rebuilds the same visual path from stored events.
1478
+
1479
+ ### Accessibility and small screens
1480
+
1481
+ The graph has an equivalent ordered outline that supports keyboard navigation,
1482
+ node selection, validation, and run inspection. Version one makes full
1483
+ drag-and-connect editing a desktop interaction. Small screens can inspect,
1484
+ filter, dry-run, simulate with existing fixtures, start an approved live run,
1485
+ and cancel it, but do not pretend that precise graph wiring is usable on a
1486
+ narrow touch viewport.
1487
+
1488
+ ## Limits
1489
+
1490
+ Initial hard limits are contract defaults and may become bounded module
1491
+ settings without changing the graph format:
1492
+
1493
+ - 100 nodes per graph;
1494
+ - 200 edges per graph;
1495
+ - 32 schemas per graph;
1496
+ - 64 KB canonical graph JSON;
1497
+ - 64 KB invocation input;
1498
+ - 64 KB one node input or output envelope;
1499
+ - 1 MB retained safe payload data per run;
1500
+ - 5 attempts per node;
1501
+ - 24 hours total live duration;
1502
+ - 10,000 run events;
1503
+ - 1,000 history rows per export page, 100 per interactive page;
1504
+ - no sub-workflow lineage in version one.
1505
+
1506
+ The worker keeps only the current node envelopes and compiled plan in memory.
1507
+ History and large safe payloads remain paged from storage.
1508
+
1509
+ ## Failure modes
1510
+
1511
+ | Failure | Required behavior |
1512
+ | ---------------------------------- | ------------------------------------------------------------------------------------------------- |
1513
+ | Agent revision removed or inactive | Refuse publish or live preflight. Never use latest. |
1514
+ | Action missing or version changed | Refuse before invocation. Never invoke a nearby contract. |
1515
+ | Action is external or destructive | Exclude it from the catalog and refuse publication or invocation in version one. |
1516
+ | Permission missing | Refuse the node before child or action work. |
1517
+ | Invalid mapping or schema | Dry-run or publish error; runtime invalid data follows explicit fail policy. |
1518
+ | Provider timeout | Record child correlation, apply bounded retry policy, preserve idempotency. |
1519
+ | Action timeout | Observe the action idempotency record before any retry. |
1520
+ | Action ignores cancellation | Record non-acknowledgement, observe or time out the accepted invocation, and discard late output. |
1521
+ | Worker crash | Recover lease and resume from committed node intent. |
1522
+ | Duplicate enqueue | Return the original matching run or idempotency conflict. |
1523
+ | SSE disconnect | Execution continues and stream resumes from the run-bound cursor or matching sequence. |
1524
+ | Invalid history or event cursor | Refuse the page or stream without leaking another tenant, run, or filter set. |
1525
+ | Unknown event schema or transition | Stop projection and recovery with a stable refusal. Never skip or guess. |
1526
+ | Payload too large | Refuse before persistence or truncate only a declared preview, never the value used by execution. |
1527
+ | Audit append failure | Do not commit the state transition that requires the audit event. |
1528
+ | Retention failure | Keep payload and retry later. Never delete before its retention event commits. |
1529
+ | Module absent | Capability lookup returns null and caller degrades explicitly. |
1530
+
1531
+ ## Guarantees
1532
+
1533
+ Consumers may rely on:
1534
+
1535
+ - immutable published revisions and exact graph checksums;
1536
+ - exact pinned agent revision and action contract version;
1537
+ - action nodes are limited to `read` and `workspace-write` risk in version one;
1538
+ - DAG-only validation and one successful execution per node;
1539
+ - deterministic sequential topological scheduling in engine version one;
1540
+ - durable enqueue before acceptance;
1541
+ - tenant isolation and immutable authorization evidence;
1542
+ - stable idempotency behavior;
1543
+ - ordered append-only run events and resumable streams;
1544
+ - a versioned event catalog with legal run, node, and immutable attempt
1545
+ projections;
1546
+ - full status, node attempt, edge, actor, mode, revision, usage, cost, failure,
1547
+ refusal, cancellation, and audit history while payload retention is separate;
1548
+ - dry-run has a typed response and no invocation history, writes, or effects;
1549
+ - simulation has separate recorded and virtual time, not-applicable usage and
1550
+ cost, and no provider, action, network, business write, or real sleep;
1551
+ - opaque tenant and filter-bound history cursors and run-bound SSE cursors;
1552
+ - no arbitrary executable graph configuration.
1553
+
1554
+ ## Deliberately unspecified
1555
+
1556
+ Version one does not promise:
1557
+
1558
+ - parallel execution of ready branches;
1559
+ - wall-clock timing between persisted events;
1560
+ - provider output determinism;
1561
+ - implicit JSON Schema compatibility beyond runtime validation;
1562
+ - retention of safe payload blobs after their configured expiry;
1563
+ - availability of an archived agent revision unless agents.core advertises it
1564
+ as retained and executable;
1565
+ - action cost unless the action contract explicitly reports it;
1566
+ - mobile drag-and-connect editing;
1567
+ - cyclic graphs, waiting for human approval inside a run, subflows, compensation,
1568
+ distributed transactions, or exactly-once external systems.
1569
+
1570
+ The engine provides effectively-once invocation through durable idempotency.
1571
+ The target action remains responsible for its own idempotent business effect.
1572
+
1573
+ ## Rejected alternatives
1574
+
1575
+ ### Put workflows inside agents.core
1576
+
1577
+ Rejected. Agent definitions and one-agent runs remain useful without a graph
1578
+ editor or workflow worker. This placement adds idle and cognitive cost to every
1579
+ agent installation and makes agents own process semantics.
1580
+
1581
+ ### Put workflows inside automations.core
1582
+
1583
+ Rejected. Manual calls, module calls, and agent calls do not require a schedule
1584
+ or webhook. Automations owns time and ingress, not orchestration graphs.
1585
+
1586
+ ### Store a list of agent ids only
1587
+
1588
+ Rejected. It cannot represent validation, typed data, branch decisions, module
1589
+ actions, failure routes, or audit evidence. It also encourages latest-revision
1590
+ execution and ambiguous data passing.
1591
+
1592
+ ### General cyclic process engine in version one
1593
+
1594
+ Rejected. Cycles make boundedness, recovery, cancellation, data retention, and
1595
+ visual reasoning materially harder. Bounded retry is explicit node policy, not
1596
+ a graph back edge. A future loop node needs its own limit and spec.
1597
+
1598
+ ### Arbitrary JavaScript or expression nodes
1599
+
1600
+ Rejected. They bypass module contracts, permissions, deterministic validation,
1601
+ and CSP, and turn graph data into executable code. Typed bindings and allowlisted
1602
+ gate logic cover the stated need.
1603
+
1604
+ ### Arbitrary HTTP action nodes
1605
+
1606
+ Rejected. They create an SSRF and secret-management surface and bypass target
1607
+ module authorization and audit. Actions must be registered, versioned module
1608
+ contracts.
1609
+
1610
+ ### A second workflow action registry beside agent tools
1611
+
1612
+ Rejected for version one. Business modules already register bounded tools with
1613
+ permissions, input schema, timeout, and output limits. Extending that contract
1614
+ with version and idempotency costs less than maintaining two operation catalogs.
1615
+
1616
+ ### Let published workflows use the latest agent revision
1617
+
1618
+ Rejected. A workflow could change behavior without a workflow revision, making
1619
+ simulation, audit, and rollback claims false. Exact revision execution is a
1620
+ publication prerequisite.
1621
+
1622
+ ### Treat schedules as anonymous system users
1623
+
1624
+ Rejected. It loses the configuring human and produces inconsistent target
1625
+ module history. A service actor plus separate origin is explicit and auditable.
1626
+
1627
+ ## Staged delivery
1628
+
1629
+ ### Stage 0: prerequisites
1630
+
1631
+ 1. Extend kernel `Actor` with `service` and update record history.
1632
+ 2. Add immutable `agent_definition_revisions`, or an equivalent executable
1633
+ snapshot store, owned by `agents.core`, including safe adoption of each
1634
+ current definition as its first retained revision.
1635
+ 3. Register the additive `agents.run-execution.v2` capability with exact
1636
+ revision enqueue, event observation, terminal result, cancellation, and
1637
+ structured JSON Schema output. Keep `agents.run-queue` unchanged.
1638
+ 4. Add structured output support to `@flowdular/sdk/harness` and provider capability
1639
+ discovery. Refuse an agent-decision node when its pinned model cannot produce
1640
+ the required structured output.
1641
+ 5. Add trusted `agentId` and `agentName` fields to `AgentToolContext`, sourced
1642
+ from the immutable run snapshot, so workflow calls from tools retain the
1643
+ actual agent actor and authorizing run.
1644
+ 6. Add optional contract version, output schema, risk, and idempotency metadata
1645
+ to registered agent tools. Existing tools remain valid for agent runs but do
1646
+ not enter the workflow action catalog until they opt in.
1647
+ 7. Register the additive `agents.actions.v1` public executor with the same
1648
+ input, permission, timeout, output, and audit guards used by agent tools.
1649
+ 8. Approve the `workflows.core` spec. Agents must not set it to approved.
1650
+
1651
+ ### Stage 1: contract, drafts, validation, and canvas
1652
+
1653
+ 1. Scaffold `workflows.core` from the approved spec.
1654
+ 2. Implement graph schema, pure compiler, issue locations, checksums, revisions,
1655
+ migrations, ACL, and tests.
1656
+ 3. Implement the canvas, inspector, outline, optimistic draft save, catalog,
1657
+ publication refusal, and dry-run.
1658
+ 4. Keep live publication disabled unless every Stage 0 capability is available.
1659
+
1660
+ ### Stage 2: deterministic simulation and history
1661
+
1662
+ 1. Implement fixture simulation with virtual time and no real effects.
1663
+ 2. Persist simulation runs, node attempts, edge evidence, events, payloads, and
1664
+ audit correlation.
1665
+ 3. Implement filters, cursor pagination, run detail, SSE resume, and canvas
1666
+ playback.
1667
+
1668
+ ### Stage 3: live agent DAGs
1669
+
1670
+ 1. Add worker leases, recovery, exact agent revision nodes, agent-decision,
1671
+ gates, validation, merge, output, retry, and cancellation.
1672
+ 2. Aggregate child usage and cost.
1673
+ 3. Prove process-loss recovery before and after agent enqueue.
1674
+
1675
+ ### Stage 4: versioned action nodes
1676
+
1677
+ 1. Invoke registered versioned `read` and `workspace-write` actions under the
1678
+ actor permission snapshot.
1679
+ 2. Prove process-loss recovery and target mutation idempotency.
1680
+ 3. Add the allowed action risk summary and live confirmation in the canvas.
1681
+
1682
+ ### Stage 5: optional automation bridge and module adoption
1683
+
1684
+ 1. Add an optional bridge that depends on workflows and automations.
1685
+ 2. Support schedule and signed webhook origins with service actors.
1686
+ 3. Adopt the execution capability in one business module as the reference call
1687
+ site.
1688
+ 4. Add an `agent-tool-design` example for a tool that starts a workflow while
1689
+ preserving the agent actor and run correlation.
1690
+
1691
+ ### Stage 6: hardening
1692
+
1693
+ 1. Load, retention, cancellation, stream resume, tenant isolation, and audit
1694
+ tamper tests.
1695
+ 2. Security review of action permissions, payload redaction, exclusion of
1696
+ external and destructive actions, and service actor provenance.
1697
+ 3. Browser verification of canvas validation, simulation playback, live status,
1698
+ reload resume, history filters, and small-screen read mode.
1699
+
1700
+ Cycles, sub-workflows, human approval nodes, compensation, parallel execution,
1701
+ and arbitrary connectors require later specs and do not enter Stage 0 through
1702
+ Stage 6 by implication.