Runyard Docs
Guides

Authoring workflows

Create and edit workflows by sending source bytes over the API or MCP; the Hub stores them as immutable, versioned bundles.

Workflows are stored in the Hub database as immutable, versioned source bundles. You create or update a workflow by sending its definition — including the workflow source bytes — over HTTP (POST /api/workflows, PATCH /api/workflows/{id}) or MCP (create_workflow / update_workflow). The Hub publishes the source as a hash-addressed bundle and records its id on the definition as workflow.bundleId.

Do NOT write workflow files to disk: custom workflow definitions that reference bare workflow.entry file paths without source bytes are rejected (workflow_source_required). Repository-authored workflow files are source templates only; when installed, the Hub publishes them as immutable DB bundles and records workflow.bundleId on the durable workflow definition. The web app's workflow editor is not an exception — it calls these same API endpoints.

The one exception: shipped presets

The ~40 workflows under workflow-templates/workflows/ are presets — the set RunYard ships with, seeded from the repo on first install so a fresh Hub is not empty. They are the reason workflow files exist in this repository at all, and they are not a precedent for adding your own.

Adding a preset is a release decision, not an authoring one. A preset only reaches a running Hub when the process restarts on new code, so it needs a tagged release and a deploy, and every operator who upgrades gets it. Adding one means editing src/seedCapability{Core,Product,Internal}.js and src/workflowTemplateIncludes.js — if you find yourself doing that for a workflow that is really just your workflow, stop and publish a bundle instead.

Everything else — anything authored for this deployment, by you or by an agent — goes to the database via the API or MCP, takes effect immediately, and never rides a release.

Both operations require an admin-scoped token.

Use audience: "product" for workflows that should appear in the default API/MCP/CLI/Web menu of team abilities. Use operations for admin/operator runbooks such as smoke checks or re-auth flows, and internal only for Hub plumbing that normal agents should not discover. Admin callers can include non-product audiences during discovery, but audience is not a security boundary; keep workflow.adminOnly, scopes, approval policy, and secret grants set for sensitive workflows.

Definition anatomy

FieldMeaning
slugStable identifier used in URLs, the CLI, and MCP (runyard run <slug>).
nameHuman-readable display name.
descriptionWhat the workflow does; shown in the catalog and menus.
categoryCatalog grouping (for example Examples, Operations).
audienceCatalog audience: product, operations, or internal. Defaults to product unless legacy seed metadata (supervision.internal, workflow.adminOnly, or Operations/Internal category) backfills a narrower audience.
inputSchemaJSON Schema for run input. Required fields drive preflight questions.
outputSchemaJSON Schema for the structured run output.
requiredRunnerTagsTags a runner must advertise to claim runs (for example ["smithers"]).
approvalPolicyWhether runs hold for a human approval before executing ({ "required": true }).
workflow.engineExecution engine; "smithers".
workflow.sourceThe workflow source bytes, sent inline as a string (aliases: workflow.sourceBytes, workflow.code).
workflow.languageSource language, defaults to tsx.

A minimal, real example

The source is a small Smithers TSX workflow (modeled on the seeded hello workflow): one task, structured output validated with Zod.

/** @jsxImportSource smithers-orchestrator */
import { createSmithers } from "smithers-orchestrator";
import { z } from "zod/v4";
import { providers } from "../agents";

const output = z.looseObject({
  answer: z.string(),
  wordCount: z.number(),
});

const inputSchema = z.object({
  topic: z.string().default("durable AI workflows"),
});

const { Workflow, Task, smithers } = createSmithers({
  input: inputSchema,
  greet: output,
});

export default smithers((ctx) => (
  <Workflow name="team-hello">
    <Task id="greet" output={output} agent={providers.claude}>
      {`Write a single vivid sentence about ${ctx.input.topic}. ` +
        `Return JSON with "answer" (the sentence) and "wordCount" (number of words in it).`}
    </Task>
  </Workflow>
));

Create the workflow by sending that source inline as workflow.source (a JSON string — the TSX above, escaped):

curl -sS -X POST https://hub.example.com/api/workflows \
  -H "Authorization: Bearer $RUNYARD_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "slug": "team-hello",
    "name": "Team hello",
    "description": "One-sentence proof workflow for our team.",
    "category": "Examples",
    "inputSchema": {
      "type": "object",
      "required": ["topic"],
      "properties": { "topic": { "type": "string" } }
    },
    "outputSchema": {
      "type": "object",
      "properties": { "answer": { "type": "string" }, "wordCount": { "type": "number" } }
    },
    "requiredRunnerTags": ["smithers"],
    "approvalPolicy": { "required": false },
    "workflow": {
      "engine": "smithers",
      "language": "tsx",
      "source": "<the TSX above, as one JSON-escaped string>"
    }
  }'

Over MCP the same payload goes to create_workflow as the workflow argument. The response definition carries workflow.bundleId instead of the inline source — the bytes now live in the Hub DB. Runners fetch the bundle at execution time; nothing needs to exist on any runner's filesystem beforehand.

Updating a workflow

Send the changed definition — again with source bytes inline — via PATCH /api/workflows/{id} or update_workflow. The Hub publishes a new bundle version and repoints workflow.bundleId; existing bundles are never mutated, so historical runs keep an exact record of what they executed. Identical bytes are deduplicated by hash rather than republished.

Inspect what is stored:

curl -sS https://hub.example.com/api/workflows/team-hello/source \
  -H "Authorization: Bearer $RUNYARD_TOKEN"        # parsed source, metadata, graph
curl -sS "https://hub.example.com/api/workflow-bundles?workflow=team-hello" \
  -H "Authorization: Bearer $RUNYARD_TOKEN"        # bundle versions (never source bytes)

The corresponding MCP tools are get_workflow_source, list_workflow_bundles, and get_workflow_bundle (which does include the source). You can also publish a bundle directly with POST /api/workflow-bundles / publish_workflow_bundle and reference it by workflow.bundleId, but prefer inline source — the Hub publishes the bundle for you.

Moving workflows between Hubs: workflow packages

Admins can export a workflow as a portable .runyard-workflow.json package file. It contains the workflow source bytes, metadata, requirements, and content hashes — never secret values.

runyard workflow-package export team-hello -o team-hello.runyard-workflow.json
runyard workflow-package validate team-hello.runyard-workflow.json   # schema + hash checks, no writes
runyard workflow-package preview team-hello.runyard-workflow.json    # requirements + resulting shape, no writes
runyard workflow-package import team-hello.runyard-workflow.json     # installs it disabled

Imports publish the package's source as a DB workflow bundle and install the workflow disabled until the receiving Hub's secrets, runners, and configuration are ready; an admin enables it with PATCH /api/workflows/{id}. The HTTP equivalents live under /api/workflow-packages/*, and MCP exposes export_workflow_package, validate_workflow_package, preview_workflow_import, and import_workflow_package.

The web editor

The web app's workflow create/edit screens are ordinary clients of POST /api/workflows and PATCH /api/workflows/{id}: they submit the definition with inline source bytes and the Hub publishes the bundle, exactly as above. There is no separate, privileged authoring path.

On this page