create-flowdular 0.2.4 → 0.2.6
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.
- package/README.md +11 -0
- package/agent-template/.agents/skills/agent-tool-design/SKILL.md +203 -0
- package/agent-template/.agents/skills/auth-security-review/SKILL.md +90 -0
- package/agent-template/.agents/skills/auto-review/SKILL.md +103 -0
- package/agent-template/.agents/skills/bug-hunt/SKILL.md +104 -0
- package/agent-template/.agents/skills/business-agent-design/SKILL.md +182 -0
- package/agent-template/.agents/skills/cli-extension/SKILL.md +108 -0
- package/agent-template/.agents/skills/core-extend/SKILL.md +99 -0
- package/agent-template/.agents/skills/database-adapter/SKILL.md +198 -0
- package/agent-template/.agents/skills/database-adapter/references/first-run-and-matrix.md +105 -0
- package/agent-template/.agents/skills/migration-authoring/SKILL.md +161 -0
- package/agent-template/.agents/skills/module-new/SKILL.md +171 -0
- package/agent-template/.agents/skills/module-update/SKILL.md +91 -0
- package/agent-template/.agents/skills/perf-audit/SKILL.md +98 -0
- package/agent-template/.agents/skills/release-eject-pr/SKILL.md +107 -0
- package/agent-template/.agents/skills/spec-approval/SKILL.md +106 -0
- package/agent-template/.agents/skills/test-hardening/SKILL.md +79 -0
- package/agent-template/.agents/skills/translations-i18n/SKILL.md +78 -0
- package/agent-template/.agents/skills/ux-design/SKILL.md +92 -0
- package/agent-template/.agents/skills/variables/SKILL.md +156 -0
- package/agent-template/.agents/skills/workflow-development/SKILL.md +192 -0
- package/agent-template/.ai/README.md +62 -0
- package/agent-template/.ai/agents/README.md +27 -0
- package/agent-template/.ai/agents/module-executor.md +36 -0
- package/agent-template/.ai/agents/reviewer.md +23 -0
- package/agent-template/.ai/agents/sandbox/agentic-engineer.md +31 -0
- package/agent-template/.ai/agents/sandbox/backend-engineer.md +36 -0
- package/agent-template/.ai/agents/sandbox/business-manager.md +23 -0
- package/agent-template/.ai/agents/sandbox/frontend-engineer.md +27 -0
- package/agent-template/.ai/agents/sandbox/ux-designer.md +23 -0
- package/agent-template/.ai/agents/spec-author.md +29 -0
- package/agent-template/.ai/blueprints/add-migration/README.md +5 -0
- package/agent-template/.ai/blueprints/add-migration/allowed-paths.yaml +23 -0
- package/agent-template/.ai/blueprints/add-migration/blueprint.json +14 -0
- package/agent-template/.ai/blueprints/add-migration/examples/invalid/input-destructive.json +6 -0
- package/agent-template/.ai/blueprints/add-migration/examples/invalid/plan-unnumbered-file.json +9 -0
- package/agent-template/.ai/blueprints/add-migration/examples/valid/input.json +6 -0
- package/agent-template/.ai/blueprints/add-migration/examples/valid/plan.json +9 -0
- package/agent-template/.ai/blueprints/add-migration/gates.yaml +30 -0
- package/agent-template/.ai/blueprints/add-migration/input.schema.json +23 -0
- package/agent-template/.ai/blueprints/add-migration/plan.schema.json +54 -0
- package/agent-template/.ai/blueprints/add-migration/required-files.yaml +18 -0
- package/agent-template/.ai/blueprints/add-migration/spec-requirements.yaml +13 -0
- package/agent-template/.ai/blueprints/add-migration/steps.yaml +62 -0
- package/agent-template/.ai/blueprints/author-spec/README.md +5 -0
- package/agent-template/.ai/blueprints/author-spec/allowed-paths.yaml +7 -0
- package/agent-template/.ai/blueprints/author-spec/blueprint.json +14 -0
- package/agent-template/.ai/blueprints/author-spec/examples/invalid/input-missing-outcome.json +5 -0
- package/agent-template/.ai/blueprints/author-spec/examples/valid/input.json +6 -0
- package/agent-template/.ai/blueprints/author-spec/gates.yaml +13 -0
- package/agent-template/.ai/blueprints/author-spec/input.schema.json +20 -0
- package/agent-template/.ai/blueprints/author-spec/plan.schema.json +14 -0
- package/agent-template/.ai/blueprints/author-spec/required-files.yaml +6 -0
- package/agent-template/.ai/blueprints/author-spec/spec-requirements.yaml +35 -0
- package/agent-template/.ai/blueprints/author-spec/steps.yaml +28 -0
- package/agent-template/.ai/blueprints/author-spec/templates/module.yaml +45 -0
- package/agent-template/.ai/blueprints/bug-fix/README.md +5 -0
- package/agent-template/.ai/blueprints/bug-fix/allowed-paths.yaml +29 -0
- package/agent-template/.ai/blueprints/bug-fix/blueprint.json +14 -0
- package/agent-template/.ai/blueprints/bug-fix/examples/invalid/input-no-symptom.json +4 -0
- package/agent-template/.ai/blueprints/bug-fix/examples/invalid/plan-no-test.json +8 -0
- package/agent-template/.ai/blueprints/bug-fix/examples/valid/input.json +6 -0
- package/agent-template/.ai/blueprints/bug-fix/examples/valid/plan.json +8 -0
- package/agent-template/.ai/blueprints/bug-fix/gates.yaml +30 -0
- package/agent-template/.ai/blueprints/bug-fix/input.schema.json +25 -0
- package/agent-template/.ai/blueprints/bug-fix/plan.schema.json +53 -0
- package/agent-template/.ai/blueprints/bug-fix/required-files.yaml +7 -0
- package/agent-template/.ai/blueprints/bug-fix/spec-requirements.yaml +7 -0
- package/agent-template/.ai/blueprints/bug-fix/steps.yaml +51 -0
- package/agent-template/.ai/blueprints/core-extend/README.md +5 -0
- package/agent-template/.ai/blueprints/core-extend/allowed-paths.yaml +49 -0
- package/agent-template/.ai/blueprints/core-extend/blueprint.json +14 -0
- package/agent-template/.ai/blueprints/core-extend/examples/invalid/input-unknown-package.json +5 -0
- package/agent-template/.ai/blueprints/core-extend/examples/invalid/plan-missing-gates.json +7 -0
- package/agent-template/.ai/blueprints/core-extend/examples/valid/input.json +6 -0
- package/agent-template/.ai/blueprints/core-extend/examples/valid/plan.json +10 -0
- package/agent-template/.ai/blueprints/core-extend/gates.yaml +16 -0
- package/agent-template/.ai/blueprints/core-extend/input.schema.json +54 -0
- package/agent-template/.ai/blueprints/core-extend/plan.schema.json +39 -0
- package/agent-template/.ai/blueprints/core-extend/required-files.yaml +36 -0
- package/agent-template/.ai/blueprints/core-extend/spec-requirements.yaml +17 -0
- package/agent-template/.ai/blueprints/core-extend/steps.yaml +54 -0
- package/agent-template/.ai/blueprints/edit-module/README.md +9 -0
- package/agent-template/.ai/blueprints/edit-module/allowed-paths.yaml +27 -0
- package/agent-template/.ai/blueprints/edit-module/blueprint.json +20 -0
- package/agent-template/.ai/blueprints/edit-module/examples/invalid/input-unknown-change.json +5 -0
- package/agent-template/.ai/blueprints/edit-module/examples/invalid/plan-touches-platform.json +15 -0
- package/agent-template/.ai/blueprints/edit-module/examples/valid/input.json +5 -0
- package/agent-template/.ai/blueprints/edit-module/examples/valid/plan.json +25 -0
- package/agent-template/.ai/blueprints/edit-module/gates.yaml +30 -0
- package/agent-template/.ai/blueprints/edit-module/input.schema.json +31 -0
- package/agent-template/.ai/blueprints/edit-module/plan.schema.json +65 -0
- package/agent-template/.ai/blueprints/edit-module/required-files.yaml +80 -0
- package/agent-template/.ai/blueprints/edit-module/spec-requirements.yaml +15 -0
- package/agent-template/.ai/blueprints/edit-module/steps.yaml +115 -0
- package/agent-template/.ai/blueprints/new-module/README.md +7 -0
- package/agent-template/.ai/blueprints/new-module/allowed-paths.yaml +27 -0
- package/agent-template/.ai/blueprints/new-module/blueprint.json +20 -0
- package/agent-template/.ai/blueprints/new-module/examples/invalid/input-spec-outside-modules.json +4 -0
- package/agent-template/.ai/blueprints/new-module/examples/invalid/plan-unknown-gate.json +8 -0
- package/agent-template/.ai/blueprints/new-module/examples/valid/input.json +5 -0
- package/agent-template/.ai/blueprints/new-module/examples/valid/plan.json +22 -0
- package/agent-template/.ai/blueprints/new-module/gates.yaml +30 -0
- package/agent-template/.ai/blueprints/new-module/input.schema.json +21 -0
- package/agent-template/.ai/blueprints/new-module/plan.schema.json +58 -0
- package/agent-template/.ai/blueprints/new-module/required-files.yaml +73 -0
- package/agent-template/.ai/blueprints/new-module/spec-requirements.yaml +30 -0
- package/agent-template/.ai/blueprints/new-module/steps.yaml +138 -0
- package/agent-template/.ai/blueprints/release/README.md +5 -0
- package/agent-template/.ai/blueprints/release/allowed-paths.yaml +19 -0
- package/agent-template/.ai/blueprints/release/blueprint.json +14 -0
- package/agent-template/.ai/blueprints/release/examples/invalid/input-bad-version.json +4 -0
- package/agent-template/.ai/blueprints/release/examples/invalid/plan-bad-branch.json +7 -0
- package/agent-template/.ai/blueprints/release/examples/valid/input.json +5 -0
- package/agent-template/.ai/blueprints/release/examples/valid/plan.json +20 -0
- package/agent-template/.ai/blueprints/release/gates.yaml +20 -0
- package/agent-template/.ai/blueprints/release/input.schema.json +24 -0
- package/agent-template/.ai/blueprints/release/plan.schema.json +46 -0
- package/agent-template/.ai/blueprints/release/required-files.yaml +19 -0
- package/agent-template/.ai/blueprints/release/spec-requirements.yaml +8 -0
- package/agent-template/.ai/blueprints/release/steps.yaml +47 -0
- package/agent-template/.ai/blueprints/security-review/README.md +5 -0
- package/agent-template/.ai/blueprints/security-review/allowed-paths.yaml +6 -0
- package/agent-template/.ai/blueprints/security-review/blueprint.json +14 -0
- package/agent-template/.ai/blueprints/security-review/examples/invalid/input-unknown-kind.json +4 -0
- package/agent-template/.ai/blueprints/security-review/examples/invalid/plan-finding-without-scenario.json +14 -0
- package/agent-template/.ai/blueprints/security-review/examples/valid/input.json +4 -0
- package/agent-template/.ai/blueprints/security-review/examples/valid/plan.json +19 -0
- package/agent-template/.ai/blueprints/security-review/gates.yaml +22 -0
- package/agent-template/.ai/blueprints/security-review/input.schema.json +20 -0
- package/agent-template/.ai/blueprints/security-review/plan.schema.json +65 -0
- package/agent-template/.ai/blueprints/security-review/required-files.yaml +6 -0
- package/agent-template/.ai/blueprints/security-review/spec-requirements.yaml +9 -0
- package/agent-template/.ai/blueprints/security-review/steps.yaml +38 -0
- package/agent-template/.ai/examples/README.md +8 -0
- package/agent-template/.ai/examples/bad/client-imports-server/README.md +20 -0
- package/agent-template/.ai/examples/bad/client-imports-server/api.ts +12 -0
- package/agent-template/.ai/examples/bad/missing-acl/README.md +23 -0
- package/agent-template/.ai/examples/bad/missing-acl/endpoints.ts +12 -0
- package/agent-template/.ai/examples/bad/tenant-from-body/README.md +19 -0
- package/agent-template/.ai/examples/bad/tenant-from-body/endpoints.ts +33 -0
- package/agent-template/.ai/examples/client-contribution/CustomerListView.tsrx +34 -0
- package/agent-template/.ai/examples/client-contribution/README.md +11 -0
- package/agent-template/.ai/examples/client-contribution/contribution.tsrx +48 -0
- package/agent-template/.ai/examples/client-contribution/index.ts +20 -0
- package/agent-template/.ai/examples/client-contribution/permissions.ts +8 -0
- package/agent-template/.ai/examples/customer-cli-extension/README.md +14 -0
- package/agent-template/.ai/examples/customer-cli-extension/commands.json +17 -0
- package/agent-template/.ai/examples/customer-cli-extension/index.ts +36 -0
- package/agent-template/.ai/examples/module-create/task-packet.json +11 -0
- package/agent-template/.ai/guides/application-development.md +97 -0
- package/agent-template/.ai/policies/capabilities.yaml +164 -0
- package/agent-template/.ai/policies/model-routing.yaml +72 -0
- package/agent-template/.ai/policies/path-ownership.yaml +65 -0
- package/agent-template/.ai/policies/task-budgets.yaml +37 -0
- package/agent-template/.ai/references/catalog/LICENSE +21 -0
- package/agent-template/.ai/references/catalog/migrations/0001_catalog_core.down.sql +2 -0
- package/agent-template/.ai/references/catalog/migrations/0001_catalog_core.up.sql +21 -0
- package/agent-template/.ai/references/catalog/migrations/0002_catalog_history.down.sql +3 -0
- package/agent-template/.ai/references/catalog/migrations/0002_catalog_history.up.sql +20 -0
- package/agent-template/.ai/references/catalog/migrations/0003_catalog_history_service_actors.down.sql +3 -0
- package/agent-template/.ai/references/catalog/migrations/0003_catalog_history_service_actors.up.sql +36 -0
- package/agent-template/.ai/references/catalog/migrations/0004_catalog_idempotency_ledger.down.sql +3 -0
- package/agent-template/.ai/references/catalog/migrations/0004_catalog_idempotency_ledger.up.sql +19 -0
- package/agent-template/.ai/references/catalog/migrations/README.md +3 -0
- package/agent-template/.ai/references/catalog/module.json +27 -0
- package/agent-template/.ai/references/catalog/package.json +49 -0
- package/agent-template/.ai/references/catalog/spec/module.yaml +86 -0
- package/agent-template/.ai/references/catalog/src/acl/permissions.ts +6 -0
- package/agent-template/.ai/references/catalog/src/agent/tools.ts +164 -0
- package/agent-template/.ai/references/catalog/src/api/endpoints.ts +243 -0
- package/agent-template/.ai/references/catalog/src/client/CatalogHistoryDrawer.tsrx +123 -0
- package/agent-template/.ai/references/catalog/src/client/CatalogItemForm.tsrx +190 -0
- package/agent-template/.ai/references/catalog/src/client/CatalogView.tsrx +473 -0
- package/agent-template/.ai/references/catalog/src/client/api.ts +111 -0
- package/agent-template/.ai/references/catalog/src/client/contribution.tsrx +61 -0
- package/agent-template/.ai/references/catalog/src/client/index.ts +18 -0
- package/agent-template/.ai/references/catalog/src/client/navigation-copy.ts +9 -0
- package/agent-template/.ai/references/catalog/src/client/state.ts +24 -0
- package/agent-template/.ai/references/catalog/src/domain/types.ts +32 -0
- package/agent-template/.ai/references/catalog/src/domain/variables.ts +111 -0
- package/agent-template/.ai/references/catalog/src/index.ts +31 -0
- package/agent-template/.ai/references/catalog/src/platform.ts +35 -0
- package/agent-template/.ai/references/catalog/src/server/index.ts +4 -0
- package/agent-template/.ai/references/catalog/src/server/runtime.ts +86 -0
- package/agent-template/.ai/references/catalog/src/services/catalog-service.ts +306 -0
- package/agent-template/.ai/references/catalog/src/services/database-repository.ts +440 -0
- package/agent-template/.ai/references/catalog/src/services/index.ts +4 -0
- package/agent-template/.ai/references/catalog/src/services/migration.ts +171 -0
- package/agent-template/.ai/references/catalog/src/services/repository.ts +36 -0
- package/agent-template/.ai/references/catalog/src/services/target-idempotency.ts +59 -0
- package/agent-template/.ai/references/catalog/tests/agent-tools.test.ts +277 -0
- package/agent-template/.ai/references/catalog/tests/endpoints.test.ts +320 -0
- package/agent-template/.ai/references/catalog/tests/idempotency.test.ts +297 -0
- package/agent-template/.ai/references/catalog/tests/migrations.test.ts +149 -0
- package/agent-template/.ai/references/catalog/tests/module.test.ts +271 -0
- package/agent-template/.ai/references/catalog/tests/support/database.ts +76 -0
- package/agent-template/.ai/references/catalog/translations/en.json +101 -0
- package/agent-template/.ai/references/catalog/translations/pl.json +101 -0
- package/agent-template/.ai/references/catalog/tsconfig.json +15 -0
- package/agent-template/.ai/references/catalog/vitest.config.ts +16 -0
- package/agent-template/.ai/references/catalog.provenance.json +55 -0
- package/agent-template/.ai/rules/flowdular.md +86 -0
- package/agent-template/.ai/skills/README.md +36 -0
- package/agent-template/.ai/skills/agent-tool-design/SKILL.md +209 -0
- package/agent-template/.ai/skills/auth-security-review/SKILL.md +96 -0
- package/agent-template/.ai/skills/auto-review/SKILL.md +112 -0
- package/agent-template/.ai/skills/bug-hunt/SKILL.md +110 -0
- package/agent-template/.ai/skills/business-agent-design/SKILL.md +188 -0
- package/agent-template/.ai/skills/cli-extension/SKILL.md +114 -0
- package/agent-template/.ai/skills/core-extend/SKILL.md +104 -0
- package/agent-template/.ai/skills/database-adapter/SKILL.md +204 -0
- package/agent-template/.ai/skills/database-adapter/references/first-run-and-matrix.md +105 -0
- package/agent-template/.ai/skills/migration-authoring/SKILL.md +167 -0
- package/agent-template/.ai/skills/module-new/SKILL.md +180 -0
- package/agent-template/.ai/skills/module-update/SKILL.md +100 -0
- package/agent-template/.ai/skills/perf-audit/SKILL.md +105 -0
- package/agent-template/.ai/skills/release-eject-pr/SKILL.md +113 -0
- package/agent-template/.ai/skills/spec-approval/SKILL.md +112 -0
- package/agent-template/.ai/skills/test-hardening/SKILL.md +86 -0
- package/agent-template/.ai/skills/translations-i18n/SKILL.md +85 -0
- package/agent-template/.ai/skills/ux-design/SKILL.md +97 -0
- package/agent-template/.ai/skills/variables/SKILL.md +164 -0
- package/agent-template/.ai/skills/workflow-development/SKILL.md +199 -0
- package/agent-template/.claude/skills/agent-tool-design/SKILL.md +203 -0
- package/agent-template/.claude/skills/auth-security-review/SKILL.md +90 -0
- package/agent-template/.claude/skills/auto-review/SKILL.md +103 -0
- package/agent-template/.claude/skills/bug-hunt/SKILL.md +104 -0
- package/agent-template/.claude/skills/business-agent-design/SKILL.md +182 -0
- package/agent-template/.claude/skills/cli-extension/SKILL.md +108 -0
- package/agent-template/.claude/skills/core-extend/SKILL.md +99 -0
- package/agent-template/.claude/skills/database-adapter/SKILL.md +198 -0
- package/agent-template/.claude/skills/database-adapter/references/first-run-and-matrix.md +105 -0
- package/agent-template/.claude/skills/migration-authoring/SKILL.md +161 -0
- package/agent-template/.claude/skills/module-new/SKILL.md +171 -0
- package/agent-template/.claude/skills/module-update/SKILL.md +91 -0
- package/agent-template/.claude/skills/perf-audit/SKILL.md +98 -0
- package/agent-template/.claude/skills/release-eject-pr/SKILL.md +107 -0
- package/agent-template/.claude/skills/spec-approval/SKILL.md +106 -0
- package/agent-template/.claude/skills/test-hardening/SKILL.md +79 -0
- package/agent-template/.claude/skills/translations-i18n/SKILL.md +78 -0
- package/agent-template/.claude/skills/ux-design/SKILL.md +92 -0
- package/agent-template/.claude/skills/variables/SKILL.md +156 -0
- package/agent-template/.claude/skills/workflow-development/SKILL.md +192 -0
- package/agent-template/AGENTS.md +77 -0
- package/agent-template/CLAUDE.md +77 -0
- package/agent-template/docs/adr/0001-development-reload.md +16 -0
- package/agent-template/docs/adr/0002-durable-agent-execution.md +21 -0
- package/agent-template/docs/adr/0003-module-settings.md +22 -0
- package/agent-template/docs/adr/0004-enterprise-access-and-audit.md +36 -0
- package/agent-template/docs/adr/0005-sandbox-runtime-and-coding-agents.md +81 -0
- package/agent-template/docs/adr/0006-agentic-workflows.md +1702 -0
- package/agent-template/docs/adr/0007-module-owned-agents.md +429 -0
- package/agent-template/docs/adr/0008-database-adapter-contract.md +90 -0
- package/agent-template/docs/agent-contract.md +45 -0
- package/agent-template/docs/configuration.md +122 -0
- package/agent-template/docs/database-adapters.md +346 -0
- package/agent-template/docs/design-system.md +217 -0
- package/agent-template/docs/modules.md +146 -0
- package/agent-template/platform/scripts/build.mjs +38 -0
- package/agent-template/rulesync.jsonc +11 -0
- package/dist/bin.js +40 -2
- package/package.json +3 -2
- package/template/default/.prettierignore +9 -0
- package/template/default/README.md +12 -0
- package/template/default/flowdular.json +3 -3
- package/template/default/modules/example/package.json +2 -2
- package/template/default/package.json +6 -2
- package/template/default/platform/octane.config.ts +17 -6
- package/template/default/platform/package.json +3 -2
- package/template/default/pnpm-workspace.yaml +1 -0
|
@@ -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.
|