Core Philosophy
Vyuh Workflows separates orchestration, side effects, history, and storage. The executable source is a Dart function or a constrained JSON document. The runtime never stores a Dart stack frame.
Definition Before Implementation
The public boundary and the executable body are different concepts:
| Noun | Responsibility |
|---|---|
Schema<T> | Describes a reusable data contract. It can be shared by entities, events, configurations, stored records, workflows, and operations. |
Workflow<I, O, E> | Assigns input, output, and expected-failure schemas to an immutable workflow identity. |
WorkflowHandler<I, O, E> | Implements the definition with deterministic orchestration. |
Operation<I, O, E> | Assigns schemas to one versioned automated side-effect boundary. |
OperationHandler<I, O, E> | Performs the real external effect. |
Work<I, R> | Declares assigned work with a typed request and response. |
Event<P> | Declares a typed fact sent into a workflow run. |
A definition says what may execute. A handler says how it executes. A payload schema belongs to the payload, not to a workflow.
Replay, Not Tokens
A run is history plus the current command projection. After every durable fact the runtime invokes the same versioned workflow from its entry point:
Completed awaits return recorded results. Unresolved awaits emit a command and mark the run blocked. running means a new fact is ready to reduce, not that a Dart isolate is parked.
Side Effects Belong in Operations
Workflow code may use if, switch, and loops over recorded values. It must not:
- call a network or database;
- read the wall clock or generate randomness;
- mutate globals;
- use
Future.waitor unregistered futures.
Those belong in versioned OperationHandlers that receive a stable idempotencyKey. Assigned work uses request and respond, regardless of whether the actor is a person, group, role, agent, bot, or service. External communication uses waitForEvent and sendEvent. Time uses sleep.
One Vocabulary, Two Authoring Surfaces
| Concept | Dart | JSON | Runtime record |
|---|---|---|---|
| Identity | Workflow(code, version, ...) | code + version | version plus run |
| Automated work | flow.perform | operation | operation command |
| Assigned work | flow.request | work | work command and WorkItem |
| External event | flow.waitForEvent | event | event history |
| Time | flow.sleep | timer | timer command |
| Branch / loop | Dart control flow | switch, joins | replay decisions |
JSON compiles to an executable handler. Arbitrary Dart cannot be converted losslessly back into a graph.
Identity Is Immutable
code + version is the public identity. The fingerprint/digest is an integrity check. A run stores all three. Changing behavior requires a new version. Workers refuse to replay history against a different digest.
Storage Is the Coordinator
Correctness comes from:
- append-only history;
- unique
(run, operationKey, attempt)commands; - optimistic run revisions;
- leases and claim-generation fencing;
- atomic join and continue-as-new transactions.
Process affinity is not a correctness mechanism. API, decider, operation, and recovery roles can share a process or run separately.
Products Talk to the Service
Product Flutter apps and product APIs do not embed deciders or hold a service-role key. They call vyuh_workflow_service. cdx_feature_workflows renders inbox state. cdx_workflow_templates supplies the canonical approval definition.
The graph subsystem is named explicitly through LegacyWorkflowEngine and other Legacy* types. It is a separate surface, not a compatibility alias in the durable API.