Scrydon
Authoring: Workflows

Workflows

Author executable workflow definitions and ship them inside a Scrydon Pack alongside a Process Flow

Three "workflow" surfaces exist: building workflows interactively in the editor (Blocks, Triggers, Execution), the YAML Reference for the editor's import/export format, and this page — authoring workflow definitions that ship inside a Pack. For the Pack build/upload lifecycle, see Packs & Authoring SDK.

A Workflow is an executable block-graph that runs alongside Process Flows — a HITL gate, an approval routing, a multi-step automation. The Workflow Authoring SDK lets you ship workflows inside the same .scrydon-pack.tar.gz archive as your Process Template, so a single upload installs the ontology, every workflow the flow needs, and the flow itself in one atomic transaction.

Workflows shipped in a Pack are pure JSON — the archive carries the block / edge / sub-block graph, never code. The workflow runtime (blocks, executors, the orchestrator) lives in the platform and is invoked by workflowId when a workflow action runs.

When to use this SDK

Use the Workflow Authoring SDK when:

  • A Process Template you ship has an action of actionType: "workflow" and you want the workflow to live in the same Pack (so re-importing the pack also refreshes the workflow).
  • You're building a demo / starter Pack and don't want to require operators to wire up workflows by hand after install.
  • You want pack-level idempotency — re-uploading the same .scrydon-pack.tar.gz updates the workflow rows in place.

If the workflow already exists in your tenant (system workflow or hand-authored), keep using actionType: "workflow" + workflowId on the action template — no Pack changes needed.

Install

bun add -d @scrydon/sdk-authoring zod
npm install --save-dev @scrydon/sdk-authoring zod
import {
  defineWorkflow,
  defineBlock,
  defineEdge,
} from '@scrydon/sdk-authoring/workflows'

Anatomy of a workflow

ElementPurpose
PackageIdentity of the workflow manifest: id, name, version
Workflow EntryOne named workflow with slug, name, version, definition — a pack may ship many
BlockA node in the executable graph (starter, human_in_the_loop, an integration block, …). Carries subBlocks of authored config
Sub-blockA single authored field on a block (reviewerRole, resolutionMode, …). The type is open-ended so the editor can evolve independently
EdgeA directed connection between blocks. May carry an optional sourceHandle / targetHandle and data
Loop / ParallelOptional sub-flow descriptors that group nodes for iteration or fan-out
VariableA workflow-scoped variable surfaced in the variables modal at runtime
Metadataname, description, exportedAt — non-functional fields preserved by the runtime

A complete example — HITL gate

This example mirrors the HITL gate that PR #1208 demos: a single human-in-the-loop block that gates the transition from Realize → Deploy. The Process Template (next section) references it by slug.

import { defineWorkflow } from '@scrydon/sdk-authoring/workflows'

export const realizeGate = defineWorkflow({
  slug: 'hitl-realize-gate',
  name: 'HITL — Realize → Deploy Gate',
  description: 'Approval gate before transitioning from Realize to Deploy.',
  version: '1.0.0',
  definition: {
    blocks: {
      'block-start': {
        id: 'block-start',
        type: 'starter',
        name: 'Start',
        position: { x: 0, y: 0 },
        subBlocks: {},
        outputs: {},
        enabled: true,
      },
      'block-hitl': {
        id: 'block-hitl',
        type: 'human_in_the_loop',
        name: 'Realize → Deploy Gate',
        position: { x: 240, y: 0 },
        subBlocks: {
          reviewerRole: {
            id: 'reviewerRole',
            type: 'short-input',
            value: 'admin',
          },
          resolutionMode: {
            id: 'resolutionMode',
            type: 'dropdown',
            value: 'first_claim',
          },
        },
        outputs: {
          approved: 'boolean',
          notes: 'string',
        },
        enabled: true,
      },
    },
    edges: [
      { id: 'edge-start-hitl', source: 'block-start', target: 'block-hitl' },
    ],
    loops: {},
    parallels: {},
  },
})

defineWorkflow, defineBlock, and defineEdge are identity functions at runtime — they narrow types so your IDE catches a missing field or a typo before the build ever runs.

The slug-rebind contract

Inside a Pack, workflows and Process Templates are decoupled through a pack-local slug. The Process Template references a workflow by workflowSlug; the pack importer materializes the workflow into the tenant, gets a real workflow id back, and rebinds the action template:

// In the Process Template:
defineAction({
  name: 'Realize → Deploy gate',
  actionType: 'workflow',
  isRequired: true,
  workflowSlug: 'hitl-realize-gate',   // ← references the workflow by pack-local slug
})

The importer walks the manifest, looks up hitl-realize-gate in the resolved slug → id map produced by the workflow install phase, and mutates the action template in-place: workflowId is set to the materialized id and workflowSlug is cleared. From that point on the action behaves like any other workflow action.

workflowId and workflowSlug are mutually exclusive on an action template — the schema rejects manifests that declare both. Use workflowId when you're pointing at a pre-existing workflow in the tenant; use workflowSlug when the workflow ships in the same Pack.

If the slug doesn't resolve (the pack didn't ship a workflow-<slug>/ subdir for it, or the manifest inside named something else), the install fails fast with UNKNOWN_WORKFLOW_SLUG. Likewise, two workflow subdirs claiming the same slug fail with DUPLICATE_WORKFLOW_SLUG.

Packaging into a Pack

A Pack may carry an ontology, one Process Template, and zero or more workflows. Each workflow lives in its own workflow-<slug>/ subdir with a manifest.json produced from defineWorkflow. The top-level pack.json lists every workflow as a separate contents[] entry:

import { defineScrydonPack } from '@scrydon/sdk-authoring/packs'

export default defineScrydonPack({
  manifestVersion: 1,
  package: {
    id: 'sap-activate-brabant',
    name: 'SAP Activate (Brabant)',
    version: '1.0.0',
  },
  contents: [
    { kind: 'ontology',     path: 'ontology',             version: '1.0.0', required: true },
    { kind: 'workflow',     path: 'workflow-hitl-gate',   version: '1.0.0', required: true },
    { kind: 'process-flow', path: 'process-flow',         version: '1.0.0', required: true },
  ],
  installOrder: ['ontology', 'workflow', 'process-flow'],
  metadata: { isSystemPack: false, isDemoPack: true, tags: ['sap-activate'] },
})

A few rules the bundler enforces:

  • ontology and process-flow are singletons — each may appear at most once. workflow may repeat.
  • contents[].path must be unique across all entries.
  • installOrder is a permutation of the distinct kinds in contents[] — a single "workflow" entry covers every workflow subdir; they all run in the workflow phase, before process-flow so the slug map is populated when the process-flow rebind runs.

Bundle layout

<bundle>.scrydon-pack.tar.gz
├── pack.json                          # PackBundleManifestSchema (lists every subdir)
├── ontology/
│   └── manifest.json                  # OntologyManifestSchema — may be empty
├── workflow-hitl-gate/
│   └── manifest.json                  # WorkflowManifestSchema (one per workflow)
├── workflow-cutover-gate/
│   └── manifest.json                  # (optional — packs may ship many workflows)
└── process-flow/
    ├── manifest.json                  # ProcessTemplateManifestSchema
    └── assets/                        # optional — JSON / image assets

The pack inspector validates each subdir's manifest against its own Zod schema. Workflow manifests round-trip as standalone JSON — you can validate one in isolation with bunx @scrydon/sdk-authoring pack validate against a workflow manifest.json.

Block validation rules

WorkflowDefinitionSchema enforces two structural invariants beyond the per-field types:

RuleWhat it catches
blocks[key].id must equal the record keyA typo'd id that would silently break edge references
Every edge source / target must reference an existing block idA dangling edge from a deleted block

Block type is intentionally an open string — the runtime block catalog (starter, human_in_the_loop, every integration block, …) evolves independently of the SDK and the schema doesn't lock to a snapshot. Same for sub-block type: the value is preserved verbatim and surfaced to the editor.

Manifest version

Workflow manifests carry manifestVersion: 1. The build-time extractor injects this so authors don't have to.

The schema rejects:

ErrorCause
Slug must be lowercase alphanumeric with hyphensA workflow slug failed /^[a-z0-9-]+$/
Must be a valid semver stringThe workflow version is not semver
workflows[] must not contain duplicate slugsTwo entries in one manifest share a slug
blocks[key].id must equal the record keyA block's id disagrees with the record key
edges[].source / edges[].target must reference an existing block idA dangling edge after a block was deleted

Build, inspect, upload

Author each workflow with defineWorkflow. Compose the Pack with defineScrydonPack and add a contents[] entry per workflow.

bunx @scrydon/sdk-authoring pack build src/pack.ts --outDir dist
# → dist/<package.id>-<package.version>.scrydon-pack.tar.gz
bunx @scrydon/sdk-authoring pack inspect dist/sap-activate-brabant-1.0.0.scrydon-pack.tar.gz

The inspector lists every subdir, validates each manifest, and surfaces the slug map the importer will resolve.

Sign in as org admin → open Settings → Packs in the platform app → click Upload pack → drop your .scrydon-pack.tar.gz. Because the pack ships workflow content, the dialog renders a workspace environment picker — pick the env where the workflow definitions should install. Ontology and process-template content install at the org level regardless of the picker.

See ADR 2026-05-21 Unified pack upload surface for the design rationale.

The underlying agentic route also accepts uploads directly. A pack that ships workflows requires workspaceEnvironmentId on the call — workflows are scoped to a workspace environment, not the org. Pass it as a query parameter:

curl -X POST "$AGENTIC_URL/api/packs/import?organizationId=$ORG_ID&workspaceEnvironmentId=$ENV_ID" \
  -H "Cookie: $SESSION_COOKIE" \
  -F "file=@dist/sap-activate-brabant-1.0.0.scrydon-pack.tar.gz"

The route returns { ontology, workflows: { idsBySlug }, processFlow }. The idsBySlug map shows each pack-local slug rebound to its materialized workflow id, useful for follow-up audit / verification.

Idempotency

The materialized workflow row id is derived as pack:<packageId>:<slug>:<workspaceEnvironmentId>. Re-uploading the same Pack to the same workspace environment finds the existing row and (when replacePublished: true is passed) deletes + re-inserts it — workflow_blocks, workflow_edges, and workflow_subflows cascade. Different environment ids produce different rows, so the same pack can be installed side-by-side across dev / staging / prod environments.

workflow.variables is stored as Record<string, Variable> keyed by variable id. The materializer attaches the resolved workflowId to each variable on insert; authored entries omit it.

Where to next

On this page

On this page