Why Serialization Matters
Workflows execute in isolated environments:- Client code runs in your application (Node.js, Edge, etc.)
- Workflow code runs in a deterministic VM sandbox
- Step code runs in the full Node.js runtime
Serialization Format
The Workflow DevKit uses a prefix-based format system that allows self-describing payloads:- Self-describing: The World layer doesn’t need to know the format in advance
- Gradual migration: Old runs keep working when new formats are introduced
- Composability: Encryption can wrap any format (e.g., “encr” wrapping “devl”)
- Debugging: Raw data inspection immediately reveals the format
The Devalue Library
The current format (devl) uses the devalue library, which provides:
- Circular reference support: Objects that reference themselves
- Rich type support: Date, RegExp, Map, Set, typed arrays, etc.
- Extensibility: Custom reducers/revivers for framework-specific types
- Compact output: More efficient than JSON.stringify
Special Type Handling
The Workflow DevKit extends devalue to handle workflow-specific types:Reducers and Revivers
The Workflow DevKit uses a reducer/reviver pattern to customize serialization:- Reducers: Convert complex objects to serializable forms during
stringify() - Revivers: Reconstruct complex objects from serialized forms during
parse()
Common Reducers
Fromserialization.ts, the getCommonReducers() function handles types used across all boundaries:
instanceof global.X? Different execution contexts (VM vs host) have different constructor functions. We check against the global parameter to handle both contexts.
Common Revivers
Revivers reverse the transformation:Boundary-Specific Serialization
Different execution boundaries need different reducer/reviver sets:External Boundary (Client ↔ Workflow)
FromgetExternalReducers() and getExternalRevivers():
Client → Workflow (arguments):
Workflow Boundary (Workflow ↔ Step)
FromgetWorkflowReducers() and getWorkflowRevivers():
Workflow → Step (arguments):
getStepReducers(), steps can create new streams:
Step Function Serialization
Step functions can be passed as arguments and serialized: Reducer:Stream Serialization
Streams are serialized by piping data to server storage and passing stream names across boundaries:Server-Side Stream Storage
WorkflowServerWritableStream:Stream Framing
For non-byte streams (object streams), chunks are framed with length prefixes:Request/Response in VM
The workflow VM provides stub implementations ofRequest and Response that defer body parsing to steps:
Error Handling
Serialization errors are wrapped with helpful context:Custom Class Serialization
Classes can implement custom serialization using special symbols:Performance Considerations
Binary format: UsingUint8Array with format prefixes reduces string encoding overhead compared to JSON.
Stream buffering: The WorkflowServerWritableStream batches writes every 10ms to reduce database round-trips:
writeToStreamMulti sends them in a single operation.
Conclusion
The Workflow DevKit’s serialization system provides:- Rich type support: Handles complex types like streams, requests, errors, and custom classes
- Cross-boundary data transfer: Seamless serialization across client, workflow, and step contexts
- Format evolution: Prefix-based format system allows gradual migration
- Performance: Binary encoding and batched stream writes reduce overhead
- Debugging: Helpful error messages with path information