This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/docs/WORKFLOW-PACKAGES.md
Jeffrey Smith 5883cb50e2 Changeset 0.30.2 cs2 (#202)
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com>
Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
2026-03-18 18:05:58 +00:00

9.3 KiB

Workflow Packages

v0.30.2

Workflow packages bundle a workflow definition, stage configuration, and optional custom surfaces into a single .pkg file. They can be exported from one instance and imported into another.

Concepts

A workflow is a multi-stage process (intake form → AI chat → human review → completion). Each stage has a surface that renders the UI: built-in surfaces (form, chat, review) or a custom package surface via surface_pkg_id.

Exporting a Workflow

GET /api/v1/admin/workflows/:id/export

Returns a .pkg ZIP with:

manifest.json           workflow manifest (type: "workflow")
js/                     surface assets (if the workflow references package surfaces)
script.star             optional Starlark handlers

The manifest includes the full workflow definition and all stages:

{
  "id": "onboarding-flow",
  "title": "Employee Onboarding",
  "type": "workflow",
  "version": "1.0.0",
  "workflow_definition": {
    "name": "Employee Onboarding",
    "slug": "employee-onboarding",
    "description": "New hire onboarding workflow",
    "entry_mode": "public_link",
    "stages": [
      {
        "name": "Intake",
        "ordinal": 0,
        "stage_mode": "form_only",
        "history_mode": "full",
        "auto_transition": false,
        "form_template": {
          "fields": [
            { "key": "full_name", "type": "text", "label": "Full Name", "required": true },
            { "key": "email", "type": "email", "label": "Email", "required": true },
            { "key": "department", "type": "select", "label": "Department",
              "options": ["Engineering", "Sales", "Marketing"] }
          ]
        }
      },
      {
        "name": "AI Orientation",
        "ordinal": 1,
        "stage_mode": "chat_only",
        "history_mode": "full",
        "persona_id": null
      },
      {
        "name": "Manager Review",
        "ordinal": 2,
        "stage_mode": "review",
        "history_mode": "summary",
        "surface_pkg_id": "review-dashboard"
      }
    ]
  }
}

Importing a Workflow

POST /api/v1/admin/packages/install
Content-Type: multipart/form-data
file=@onboarding-flow.pkg

The install handler detects type: "workflow" and:

  1. Creates the workflow from workflow_definition
  2. Creates all stages with their configuration
  3. Preserves surface_pkg_id references (the referenced package must be installed separately)
  4. Sets the workflow as inactive (admin activates manually)

Stage Modes

Each stage has a stage_mode that determines the built-in surface:

Mode Surface Description
chat_only Chat AI conversation with the visitor
form_only Form Structured data collection, no AI
form_chat Form + Chat Form fields alongside AI chat
review Review Human reviewer sees collected data, approve/reject

Custom Stage Surfaces (surface_pkg_id)

Any stage can override its built-in surface with a custom package surface by setting surface_pkg_id to an installed package ID.

How It Works

  1. Admin sets surface_pkg_id on a stage (via dropdown in stage editor or API)
  2. When a visitor reaches that stage, the workflow template checks for surface_pkg_id
  3. If set, it dynamically loads /surfaces/{pkg_id}/js/main.js
  4. The package JS registers a surface via sw.workflow.registerSurface(name, factory)
  5. The surface is mounted into the stage container with context

Writing a Custom Surface

Create a package with js/main.js:

// js/main.js — Custom review dashboard surface
(function() {
    sw.workflow.registerSurface('review-dashboard', function(container, ctx) {
        // ctx contains:
        //   channelId    — workflow instance channel
        //   sessionId    — visitor session ID
        //   basePath     — API base path
        //   stageMode    — the stage_mode value
        //   formTemplate — form schema (if applicable)
        //   totalStages  — number of stages in workflow
        //   currentStage — current stage index (0-based)

        container.innerHTML = '<div class="review-panel">Loading...</div>';

        // Fetch workflow data
        sw.workflow.getContext(ctx.channelId).then(function(status) {
            container.innerHTML = renderReview(status);
        });

        return {
            mount: function() { /* called when surface attaches */ },
            unmount: function() { /* called when surface detaches */ }
        };
    });
})();

Package manifest:

{
  "id": "review-dashboard",
  "title": "Review Dashboard",
  "type": "surface",
  "version": "1.0.0"
}

Setting surface_pkg_id

Via admin UI:

Admin → Workflows → select workflow → click Edit on a stage → Custom Surface Package dropdown → select a package.

The dropdown shows all installed packages of type surface, workflow, or full. Stages with a custom surface show a pkg: <id> badge in the stage list.

Via API:

# Set custom surface
curl -X PUT /api/v1/workflows/:wf_id/stages/:stage_id \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Review",
    "ordinal": 2,
    "stage_mode": "review",
    "history_mode": "summary",
    "surface_pkg_id": "review-dashboard"
  }'

# Clear (revert to built-in)
curl -X PUT /api/v1/workflows/:wf_id/stages/:stage_id \
  -d '{ "name": "Review", "ordinal": 2, "stage_mode": "review",
        "history_mode": "summary", "surface_pkg_id": null }'

Note: stage update is PUT (full replace), not PATCH. All stage fields must be included in the request body.

Fallback Behavior

  • surface_pkg_id = null → built-in surface based on stage_mode
  • surface_pkg_id references a missing/disabled package → error logged, falls back to built-in surface
  • Package deleted → ON DELETE SET NULL clears the reference automatically (PostgreSQL FK constraint)

Starlark Workflow Module

Extensions with the workflow.access permission can interact with workflows from Starlark code.

workflow.get_definition(workflow_id)

Returns the workflow definition including all stages.

def on_tool_call(tool_name, params):
    wf = workflow.get_definition(params["workflow_id"])
    # wf: { id, name, slug, stages: [{ name, stage_mode, surface_pkg_id, ... }] }
    return {"stages": len(wf["stages"])}

workflow.get_stage_data(channel_id)

Returns the current stage data for a workflow instance.

data = workflow.get_stage_data(channel_id)
# data: { current_stage, stage_data, ... }

workflow.advance(channel_id)

Programmatically advance to the next stage.

workflow.advance(channel_id)

workflow.reject(channel_id, reason)

Reject the current stage and go back.

workflow.reject(channel_id, "Missing required information")

Form Template Schema

Stages with form_only or form_chat mode use form_template to define structured data collection.

Field Types

Type Renders As Validation
text Text input min_length, max_length, pattern
email Email input Format validation
number Number input min, max
date Date picker min_date, max_date
select Dropdown options array required
textarea Multi-line text min_length, max_length
checkbox Checkbox
file File upload

Field Schema

{
  "fields": [
    {
      "key": "full_name",
      "type": "text",
      "label": "Full Name",
      "required": true,
      "placeholder": "Enter your name",
      "min_length": 2,
      "max_length": 100
    },
    {
      "key": "department",
      "type": "select",
      "label": "Department",
      "required": true,
      "options": ["Engineering", "Sales", "Marketing", "HR"]
    }
  ]
}

Validation Hooks

Packages with forms.validate permission can provide custom validation:

# script.star
def validate(fields, stage_data):
    errors = []
    if fields.get("email", "").endswith("@competitor.com"):
        errors.append({"field": "email", "message": "Invalid email domain"})
    return errors  # empty list = valid

def on_submit(fields, stage_data):
    # Called after validation passes
    # Can transform or enrich data before it's saved
    return fields

Database Schema

workflow_stages table

Column PG Type SQLite Type Description
id UUID TEXT Stage ID
workflow_id UUID TEXT Parent workflow FK
ordinal INTEGER INTEGER Stage order (0-based)
name TEXT TEXT Display name
persona_id UUID TEXT AI persona FK (nullable)
assignment_team_id UUID TEXT Team assignment FK (nullable)
form_template JSONB TEXT Form field schema
stage_mode TEXT TEXT chat_only|form_only|form_chat|review
history_mode TEXT TEXT full|summary|fresh
auto_transition BOOLEAN INTEGER Auto-advance on completion
transition_rules JSONB TEXT Conditional transition config
surface_pkg_id TEXT TEXT Custom surface package FK (nullable)
created_at TIMESTAMPTZ TEXT Creation timestamp

The surface_pkg_id column has a foreign key to packages(id) with ON DELETE SET NULL in PostgreSQL. SQLite enforces FK constraints via PRAGMA foreign_keys = ON.