Skip to main content

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.

TermWhat it is
TriggerWhat starts a workflow: a manual API call, a cron schedule, or an event.
WorkflowDefThe reusable, versioned, permissioned blueprint (JSON).
WorkflowA single running instance of a WorkflowDef, with its own state and audit trail.
TaskOne step inside a workflow. There are nine built-in types.
WorkerWhatever 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:

FieldPurpose
_nameHuman-readable name.
_userTypeA stable identifier you use to look the definition up later.
_namespacesThe namespaces the definition belongs to (from ctx._namespaces).
_taskDefsThe ordered list of task definitions. The body of the workflow.
_inputParamsDeclared workflow-level inputs, supplied when a run starts.
_timeoutPolicy / _timeoutSecondsWhat 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 | CANCELED

A 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 logic FIXED, EXPONENTIAL_BACKOFF, or LINEAR, and _retryDelaySeconds.
  • Time-out policy: TIME_OUT_WF terminates the workflow when the budget is exceeded; ALERT_ONLY only 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 typeWhat it doesTypical role
SCRIPT_EXECUTIONRuns a custom JavaScript function via the script engine.Transform / enrich / persist
REST_CONNECTORCalls any HTTP API (OAuth2 / Basic / Bearer / none).External integration
HAYSTACKQueries Project Haystack servers (SkySpark, Niagara, …).BMS / IoT data
MQTT_PUBLISHPublishes to any MQTT broker.Command message / pub-sub
AI_CONVERSESends a prompt to an AI Service team.Reasoning / classification
USER_INPUTPauses the workflow to wait for a human decision.Human-in-the-loop
SWITCHBranches on a value.Content-based router / filter
FORK_JOINRuns several task chains in parallel, then rejoins.Concurrency / scatter-gather
DO_WHILERepeats 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_CONNECTOR weather fetch with SCRIPT_EXECUTION tasks that return fixed forecast JSON. This keeps the project self-contained and key-free. In production the same workflow would use a REST_CONNECTOR task or fetch() in place of each generate...WeatherData script.

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:

BindingMeaning
${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.
$.varNameA 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.