Skip to Content
Health Ai PlatformWorkflowsCreating Workflows

Creating Workflows

This guide shows the smallest practical authoring loop for a workflow in the Loop platform:

  1. Define the workflow in YAML or JSON
  2. Load it through WorkflowEngine
  3. Execute it with patient context
  4. Assert on recommendations and step results

Start with a narrow use case

A good first workflow has:

  • One trigger
  • Two or three steps
  • One decision
  • One clear output

Example: detect elevated TSH and recommend a follow-up action.

id: tsh-review-v1 name: TSH Review version: 1.0.0 triggers: - type: biomarker_result params: biomarker: TSH steps: - id: check-tsh type: check_biomarker action: type: check_value params: biomarker: TSH threshold: 2.5 operator: ">" - id: recommend-selenium type: recommend_supplement condition: "check-tsh.result === true" action: type: recommend params: supplement: Selenium dosage: 200mcg

Load and register the workflow

WorkflowEngine.load() both validates and registers the definition.

import { WorkflowEngine } from '@loop/workflow-engine'; const definition = ` id: tsh-review-v1 name: TSH Review version: 1.0.0 steps: - id: check-tsh type: check_biomarker action: type: check_value params: biomarker: TSH threshold: 2.5 operator: ">" `; const workflow = WorkflowEngine.load(definition);

Execute with real context

In practice, context usually starts from the Patient Graph and is then normalized into the shape expected by the workflow.

import { PatientGraphClientImpl } from '@loop/patient-graph-client'; import { WorkflowEngine } from '@loop/workflow-engine'; const patientGraph = new PatientGraphClientImpl({ baseUrl: process.env.PATIENT_GRAPH_API_URL!, getAuthToken: async () => process.env.PATIENT_GRAPH_API_KEY!, }); const profileResult = await patientGraph.getPatientContext('user_123'); if (!profileResult.ok) { throw new Error(profileResult.error.message); } const workflow = WorkflowEngine.load(` id: tsh-review-v1 name: TSH Review version: 1.0.0 steps: - id: check-tsh type: check_biomarker action: type: check_value params: biomarker: TSH threshold: 2.5 operator: ">" - id: notify type: send_notification condition: "check-tsh.result === true" action: type: notify params: channel: email message: "Your TSH result may need review." `); const result = await workflow.execute({ patientId: profileResult.data.externalId, data: { biomarker: { TSH: 4.2 }, medications: ['metformin'], }, });

Understand the result shape

The runtime returns a WorkflowResult with execution details.

console.log(result); /* { success: true, workflowId: 'tsh-review-v1', executedSteps: ['check-tsh', 'notify'], stepResults: { 'check-tsh': { stepId: 'check-tsh', success: true, result: { checked: true, biomarker: 'TSH', value: 4.2, threshold: 2.5, operator: '>', result: true } }, notify: { stepId: 'notify', success: true, result: { sent: true, patientId: 'user_123', channel: 'email', message: 'Your TSH result may need review.' } } }, recommendations: [], errors: [] } */

Use explicit naming

Prefer:

  • Stable workflow IDs such as thyroid-review-v1
  • Descriptive step IDs such as check-tsh and order-thyroid-panel
  • Versioned definitions instead of mutating the meaning of an old workflow

Keep context shallow and predictable

The executor reads values from context.data. Use a shape that is easy to inspect and easy to test.

const context = { patientId: 'user_123', data: { biomarker: { TSH: 4.2, 'testosterone-total': 650, }, medications: ['metformin', 'warfarin'], age: 38, genetics: { MTHFR: 'C677T/C677T', }, }, };

Account for current platform boundaries

Two boundaries matter when authoring:

run_tool is an extension point

The workflow engine validates run_tool steps and checks tool availability from context.tools, but the actual handler is still a stub.

- id: call-risk-tool type: run_tool action: type: tool params: toolId: risk-calculator
const result = await workflow.execute({ patientId: 'user_123', tools: ['risk-calculator'], });

Today this returns an execution placeholder rather than delegating into a shared @loop/tool-registry package.

Triggers are definitions, not schedulers

The triggers block describes when a workflow should run, but another system still has to decide when to execute it.

  • Validate required fields: id, name, version, steps
  • Keep each step independently understandable
  • Use condition instead of duplicating whole workflows
  • Keep side effects obvious
  • Test both positive and negative branches
  • Document the patient data shape next to the workflow

Example authoring loop

# 1. Update workflow definition in your code or fixture # 2. Run tests for the workflow engine pnpm --filter @loop/workflow-engine test # 3. Run broader checks if the workflow integrates with an app pnpm typecheck

Next steps