Skip to content

Your First Workflow

This guide walks you through deploying and executing a minimal workflow. You will:

  1. Write a workflow YAML file
  2. Deploy it to the engine
  3. Execute it
  4. Inspect the results

The Workflow

Here is the simplest possible workflow -- it runs a single echo function block with global scope (no CMS connection needed):

# A minimal workflow that runs a single function block.
# Uses seedEntity: global so it works without CMS -- ideal for getting started.
label: hello_world
name: hello_world
package: wf.example.neops.io
majorVersion: 1
minorVersion: 0
patchVersion: 0
seedEntity: global
type: workflow

steps:
  - type: functionBlock
    label: echo
    functionBlock: "fb.examples.neops.io/echo:1.0.0"
    parameters:
      message: "Hello from the neops workflow engine!"

Let's break this down:

Field Purpose
label Unique identifier within the execution (used by other steps to reference results)
name Human-readable name
package Namespace (reverse domain notation, like Java packages)
majorVersion / minorVersion / patchVersion Semantic version
seedEntity What type of entity this workflow operates on (device, interface, group, or global)
type Always workflow for the root
steps The sequence of operations to execute

The single step references the echo function block at version 1.0.0 and passes a static message. Since this is a global workflow, no device context is needed -- it runs exactly once.

Dynamic parameters with JMESPath

In device-scoped workflows, you can use JMESPath expressions like {{ context.device.hostname }} to interpolate runtime data into parameters. JMESPath is a query language for JSON -- try the interactive tutorial (about 10 minutes). You'll see this in action in the Show Version example.

Build the echo function block yourself

The echo function block used here is implemented in Python using the Worker SDK. See Your First Function Block to understand how it works and write your own.

Deploy the Workflow

Use the API to register this workflow definition with the engine:

curl -X POST http://localhost:3030/workflow-definition \
  -H "Content-Type: application/json" \
  -d @- << 'EOF'
{
  "workflow": {
    "label": "hello_world",
    "name": "hello_world",
    "package": "wf.example.neops.io",
    "majorVersion": 1,
    "minorVersion": 0,
    "patchVersion": 0,
    "seedEntity": "global",
    "type": "workflow",
    "steps": [
      {
        "type": "functionBlock",
        "label": "echo",
        "functionBlock": "fb.examples.neops.io/echo:1.0.0",
        "parameters": {
          "message": "Hello from the neops workflow engine!"
        }
      }
    ]
  }
}
EOF

Converting YAML to JSON

The REST API and Monitor App currently accept JSON only. We recommend authoring workflows in YAML for readability and converting when needed:

With yq (recommended):

# Convert and pipe directly to the API
yq -o=json hello-world.workflow.yaml | \
  jq '{workflow: .}' | \
  curl -X POST http://localhost:3030/workflow-definition \
    -H "Content-Type: application/json" -d @-

# Or just convert to a file
yq -o=json hello-world.workflow.yaml > hello-world.workflow.json

Online: Paste your YAML at json2yaml.com (works both ways) or yaml-online-parser.appspot.com.

Install yq via brew install yq, pip install yq, or see github.com/mikefarah/yq.

Using the Monitor App

You can also deploy workflows through the Monitor App at http://localhost:5173. Navigate to Workflow Definitions and use the create form.

The response returns the workflow definition with its assigned ID:

{
  "id": 1,
  "label": "hello_world",
  "name": "hello_world",
  "package": "wf.example.neops.io",
  "majorVersion": 1,
  "minorVersion": 0,
  "patchVersion": 0,
  "seedEntity": "global",
  "type": "workflow",
  "steps": [...]
}

Execute the Workflow

Trigger an execution by providing the workflow identifier:

curl -X POST http://localhost:3030/workflow-execution \
  -H "Content-Type: application/json" \
  -d '{
    "workflow": "wf.example.neops.io/hello_world:1.0.0",
    "executeOnParameters": {},
    "parameters": {}
  }'

The workflow field is the full SemVer identifier (package/name:major.minor.patch). Since this workflow uses seedEntity: global, no entity IDs are needed. For device-scoped workflows, you'd provide "executeOnParameters": { "ids": [1, 2, 3] } with CMS device IDs.

The response returns the execution with its initial state:

{
  "id": 1,
  "status": "NEW",
  "workflow": "wf.example.neops.io/hello_world:1.0.0",
  "createdAt": "2025-01-15T10:30:00.000Z"
}

Workers required

For the execution to proceed, at least one worker must be running that has the fb.examples.neops.io/echo:1.0.0 function block registered.

Quick worker setup (requires the Worker SDK):

cd /path/to/neops-worker-sdk-py
URL_BLACKBOARD=http://localhost:3030 DIR_FUNCTION_BLOCKS=examples/getting-started/echo uv run neops_worker

The worker discovers the echo function block, registers it with the engine, and starts polling for jobs. You should see log output confirming registration.

Inspect the Execution

Check the execution state:

curl http://localhost:3030/workflow-execution/active

The response shows the current status of the execution: which state it is in and which jobs have been created.

You can also track execution in real time through the Monitor App's Executions view.

What Happened?

sequenceDiagram
    participant You
    participant Engine as Workflow Engine
    participant BB as Blackboard
    participant Worker

    You->>Engine: Execute workflow
    Engine->>Engine: Resolve function blocks
    Engine->>BB: Create EXECUTE job (echo)
    Worker->>BB: Poll for jobs
    BB->>Worker: Return echo job
    Worker->>Worker: Execute echo FB
    Worker->>BB: Push result
    BB->>Engine: Job result event
    Engine->>Engine: All steps complete → COMPLETED

The engine:

  1. Resolved the echo function block reference to a registered version
  2. Created a job on the blackboard (one job since this is a global workflow)
  3. A worker polled the blackboard, picked up the job, and executed the echo function block
  4. Results flowed back, the engine advanced the workflow, and marked it COMPLETED

Device-scoped workflows

For workflows with seedEntity: device, the engine creates one job per device per step. If you execute on 100 devices with 3 steps, that's up to 300 jobs distributed across workers.


Complete the loop

You've deployed and executed a workflow. The echo function block was provided by a worker running the Worker SDK. To write your own function blocks, head to the Worker SDK Getting Started and build one from scratch. Then come back to compose it into a workflow.


Next: Dive into Concepts to understand workflows, transactions, the blackboard, and the execution model in depth.