What is the Workflow Service?
The Workflow Service is a back-end orchestration engine. It runs long-running, stateful, multi-step processes that connect Twinit services with one another and with third-party systems. Where a single script performs one unit of work, a workflow coordinates many units of work - in sequence, in parallel, in loops, and around human decisions - while tracking the state of each step and surviving restarts.
A workflow definition is composed of up to 25 tasks. It is stored and managed as JSON, and can be created and operated through the REST API, the JavaScript SDK (IafWorkflowSvc), or the Twinit IDE Extension. Every workflow is a first-class permissioned resource in the Passport Service, so the same authorization model that protects your collections and files also protects your workflows.
Typical uses include internal data pipelines (publishing, polling, routing, pausing), telemetry processing, scheduled jobs, human approvals, notifications, digital-twin lifecycle automation, and enterprise integrations.
The execution model#
Five terms describe how a workflow runs. Keep them in mind. The rest of the course relies on the difference between a definition and a run.
| Term | What it is |
|---|---|
| Trigger | What starts a workflow: a manual API call, a cron schedule, or an event. |
| WorkflowDef | The reusable, versioned, permissioned blueprint (JSON). |
| Workflow | A single running instance of a WorkflowDef, with its own state and audit trail. |
| Task | One step inside a workflow. There are nine built-in types. |
| Worker | Whatever a task reaches out to: a Twinit service, a script, or an external system. |
The flow is: a trigger fires a WorkflowDef; the runtime creates a Workflow instance; the instance runs its tasks in order, passing data between them and reaching outward to workers; the run reaches a terminal state and emits events other services can subscribe to.
WorkflowDef (the blueprint)#
A WorkflowDef is a JSON document. The fields you will use throughout the course are:
| Field | Purpose |
|---|---|
_name | Human-readable name. |
_userType | A stable identifier you use to look the definition up later. |
_namespaces | The namespaces the definition belongs to (from ctx._namespaces). |
_taskDefs | The ordered list of task definitions. The body of the workflow. |
_inputParams | Declared workflow-level inputs, supplied when a run starts. |
_timeoutPolicy / _timeoutSeconds | What happens if the run exceeds its time budget. |
A minimal definition looks like this (from the basic weather workflow you will create in Lesson 03):
const workflowDef = { _name: "Basic_Weather_Fetch_Workflow", _description: "Fetch weather for multiple cities sequentially", _namespaces: ctx._namespaces, _userType: "weather_workflow_basic_static", _timeoutPolicy: "TIME_OUT_WF", _timeoutSeconds: 300, _taskDefs: [ /* ... one object per task ... */ ]};await IafWorkflowSvc.createWorkflowDef(workflowDef, ctx);Definitions are permissioned. Authorized actions include READ, CREATE, EDIT, DELETE, RUN, SHARE, and ASSIGN, all enforced by the Passport Service.
Workflow (the run)#
Each time a WorkflowDef is started, the runtime creates one Workflow
instance. It moves through a lifecycle:
RUNNING → COMPLETED | FAILED | TIMED_OUT | PAUSED | CANCELEDA run is itself a Passport-protected resource with its own audit trail: you can query its status, its duration, and the individual result of every task. Runs emit resource events (ResourceCreated, ResourceUpdated, WorkflowCompleted, and so on) that downstream consumers can subscribe to.
Two reliability controls are declared on the definition and apply per run:
- Retries per task:
_retryCount, with retry logicFIXED,EXPONENTIAL_BACKOFF, orLINEAR, and_retryDelaySeconds. - Time-out policy:
TIME_OUT_WFterminates the workflow when the budget is exceeded;ALERT_ONLYonly records a counter and lets the run continue.
Tasks (the nine built-in types)#
Every step in a workflow is a task of one of these types. The whole course is organised around them.
| Task type | What it does | Typical role |
|---|---|---|
SCRIPT_EXECUTION | Runs a custom JavaScript function via the script engine. | Transform / enrich / persist |
REST_CONNECTOR | Calls any HTTP API (OAuth2 / Basic / Bearer / none). | External integration |
HAYSTACK | Queries Project Haystack servers (SkySpark, Niagara, …). | BMS / IoT data |
MQTT_PUBLISH | Publishes to any MQTT broker. | Command message / pub-sub |
AI_CONVERSE | Sends a prompt to an AI Service team. | Reasoning / classification |
USER_INPUT | Pauses the workflow to wait for a human decision. | Human-in-the-loop |
SWITCH | Branches on a value. | Content-based router / filter |
FORK_JOIN | Runs several task chains in parallel, then rejoins. | Concurrency / scatter-gather |
DO_WHILE | Repeats a task chain while a condition holds. | Polling / retry |
This course exercises SCRIPT_EXECUTION, FORK_JOIN, SWITCH, DO_WHILE, USER_INPUT, and AI_CONVERSE directly against the weather example. It covers REST_CONNECTOR, HAYSTACK, and MQTT_PUBLISH at the reference level in Lesson 13, since they require external endpoints.
Why static data? The course replaces the live
REST_CONNECTORweather fetch withSCRIPT_EXECUTIONtasks that return fixed forecast JSON. This keeps the project self-contained and key-free. In production the same workflow would use aREST_CONNECTORtask or fetch() in place of eachgenerate...WeatherDatascript.
How tasks pass data (expression bindings)#
Tasks do not share variables directly. Instead, a downstream task references the resolved output of an upstream task, and the runtime substitutes the value at execution time. There are three binding forms:
| Binding | Meaning |
|---|---|
${workflow.input.PARAM} | A workflow-level input declared in _inputParams and supplied when the run starts. |
${taskName._output.FIELD} | The resolved output of an earlier task, by task name. |
$.varName | A task-local variable, used when evaluating DO_WHILE loop conditions. |
For a SCRIPT_EXECUTION task, the resolved values arrive inside the script under input.actualParams. For example, a task defined as:
{ _type: "SCRIPT_EXECUTION", _name: "store_newyork_weather", _sequenceno: 2, _inputParams: { _userType: "weather_workflow_demo_static", _scriptName: "parseAndStoreWeatherFromWorkflow", weatherData: "${fetch_newyork_weather._output.scriptOutput}" }}passes the previous task's script output to parseAndStoreWeatherFromWorkflow, which reads it as input.actualParams.weatherData. Lesson 04 covers the exact mechanics, including the getParams helper the demo scripts use so they work both inside a workflow and when run directly from the Twinit IDE Extension.
Where this is going#
You now have the vocabulary: trigger, WorkflowDef, Workflow, task, worker, and bindings. The next lesson provisions a project so you have somewhere to create these resources; after that you will create and run your first workflows.