Skip to content

Workflow Object

A Workflow is the top-level document. It defines a directed graph of Nodes connected by Edges, with a designated entry point.

FieldTypeRequiredDefaultDescription
idstringREQUIREDUnique identifier for this workflow. MUST be non-empty.
namestringREQUIREDHuman-readable name. MUST be non-empty.
descriptionstringOPTIONAL""Purpose of the workflow.
entrystringREQUIREDNode ID where execution begins. MUST reference a key in nodes.
nodesRecord<string, Node>REQUIREDMap of node ID to Node definition. Keys are referenced by entry and edge from/to fields.
edgesEdge[]REQUIREDDirected edges defining execution flow and routing conditions.
skillsRecord<string, SkillDefinition>OPTIONAL{}Inline skill definitions scoped to this workflow.
rulesSource[]OPTIONAL[]Directives prepended to every node’s instruction. Organization-wide policies, coding standards.
contextSource[]OPTIONAL[]Background knowledge prepended to every node’s instruction. Architecture docs, playbooks.
workflow_typestring (enum)OPTIONALgenericWorkflow type discriminator. Routes runs to a type-specific renderer. See Workflow type.
modelstringOPTIONALimpl‑definedDefault execution model for every node. Overridable per-node. Free-text passthrough. See Nodes — Model Selection.
inputsRecord<string, InputField>OPTIONALDeclared per-run input contract. The CLI validates --input against this and applies defaults. See Inputs.

The workflow_type field tells consumers which kind of work this workflow does, so the run can be rendered with type-appropriate metrics. The field is optional. When absent, consumers MUST treat the workflow as type generic.

ValueWhat it producesTypical metrics
pr_reviewA PR comment with risks and suggestionsCaught / Calibration / Missed
e2e_testPass-fail per test, screenshots, tracesFlake rate, step failure heatmap, run duration
content_generationText or JSON artifacts (e.g. changelog drafts)Acceptance rate, regen rate, edit distance
monitorA delta report (e.g. weekly regulatory monitor)Coverage, novel-finding rate, staleness
data_syncRecords processed counts, errors, latencyThroughput, error rate, drift
genericAnything elseRun duration, step status, edge routing health

A conforming consumer:

  • MUST accept any of the values above.
  • MUST treat absent workflow_type as generic.
  • MUST reject any value not in the enum at parse time.
  • MAY ignore the field entirely if it doesn’t have type-specific rendering. Type-specific rendering is an opt-in surface; the runtime executor doesn’t change behavior based on workflow_type.

Marketplace templates published in v1+ are required to declare workflow_type so they render correctly out of the box.

workflow_type is independent of the Inputs contract. A workflow of any type MAY declare inputs, and the type discriminator does not reserve any field names. The composition rules are spelled out under Inputs and workflow_type.

id: regulatory-monitor
name: Regulatory Monitor
workflow_type: monitor
description: Weekly state-by-state regulation collection
entry: gather
nodes:
gather: { ... }
edges: [...]

The rules and context fields attach knowledge documents to the workflow. Every Source in these arrays is resolved eagerly before any node executes, and the resolved content is prepended to every node’s instruction (see Nodes — Input Augmentation).

  • rules — directives the AI model MUST follow. Coding standards, review policies, compliance requirements.
  • context — background information the AI model should consider. Architecture docs, service maps, incident history.

Both support inline text, file paths, and URLs:

rules:
- ./coding-standards.md
- https://raw.githubusercontent.com/acme/playbook/main/review-policy.md
- "Never introduce breaking API changes without a migration path."
context:
- ./ARCHITECTURE.md
- ./docs/service-map.md

Nodes can declare their own rules and context that layer on top. See Nodes — Rules & Context for cascade semantics.

When executing a workflow, you may pass runtime input parameters. Two special fields are recognized:

  • rules — A Source or array of Sources containing directives prepended to every node’s instruction. These layer on top of workflow-level and node-level rules.
  • context — A Source or array of Sources containing background information prepended to every node’s instruction. These layer on top of workflow-level and node-level context.

Runtime input rules and context resolve eagerly before any node executes. See Nodes — Input Augmentation for the full assembly order.

A workflow MAY declare a top-level inputs block describing the per-run parameters it accepts. When present, the CLI validates the caller-supplied --input <json> against this declaration before the executor runs, applies defaults for omitted optional fields, and rejects malformed input with a grouped error message.

Workflows without an inputs block accept any JSON object (back-compat with every workflow shipped before this feature).

Each entry in inputs declares one parameter:

FieldTypeRequiredDescription
typestring (enum)REQUIREDOne of string, number, boolean, string[].
descriptionstringOPTIONALHuman-readable purpose. Shown in CLI errors and cloud renderers.
requiredbooleanOPTIONALWhen true, the caller MUST provide a value. Default: false.
defaultmatches typeOPTIONALValue applied when the caller omits the field. Type-checked at parse time against type.
enumarrayOPTIONALOptional set of allowed values. Each entry MUST match the field’s type.

An InputField MUST NOT declare both required: true and a default; the combination is incoherent (a default would either silently satisfy the required check, or never fire) and is rejected at workflow load time. Authors who want the required check to fire MUST omit the default. Authors who want a fallback value MUST omit required: true.

Types are intentionally narrow. If you need richer shapes, model them as a flat object and let the receiving node parse the field. The contract MAY widen later without breaking existing YAML.

A conforming runtime MUST enforce:

  1. Required check. Each declared field with required: true MUST be present in the caller’s input. A field is “present” when the caller provides a non-null value; an explicit null (or absence of the key) MUST be treated as missing and surface the same required-field error. (A field with required: true cannot also declare a default; see InputField.)
  2. Type check. Each declared field’s value MUST match its type. number rejects NaN and ±Infinity. string[] requires every element to be a string.
  3. Enum check. When enum is declared, the value MUST equal one of the listed entries.
  4. Default fill. When a field is absent or null AND has a default, the runtime MUST substitute the default before passing the input to the executor. Because required and default are mutually exclusive, this rule only applies to optional fields.
  5. Caller wins. A caller-provided value MUST take precedence over a declared default.
  6. Undeclared keys pass through. Keys provided by the caller that aren’t declared in inputs SHOULD pass through unchanged (so CLI-injected fields like dryRun and the runtime rules/context cascade keep working).
  7. All errors at once. A validator MUST surface every missing-required and type-mismatch in a single response, not one error per call.

Cloud renderers and run dashboards MAY display the input shape a run was invoked with (key names plus their declared types), but MUST NOT ship the input values themselves. A workflow that accepts a token or other secret as input never leaks the value to telemetry.

A conforming runtime that posts run-start telemetry MUST transmit the shape under the payload key inputs_shape (key name → declared type, e.g. { "since_tag": "string", "draft": "boolean" }) and MUST NOT include the matching inputs (or any equivalent values bag) in the same or any other telemetry payload. Runtimes that do not transmit input data at all trivially satisfy this rule and MAY omit inputs_shape entirely.

The inputs contract and the workflow_type discriminator are orthogonal. They do not introspect each other. Concretely:

  1. Any workflow type can declare inputs. pr_review, e2e_test, content_generation, monitor, data_sync, and generic workflows all accept an inputs block. There is no type for which inputs is illegal, and no type that requires inputs. The validator does not gate inputs on workflow_type.
  2. Inputs are purely additive. Declaring inputs does NOT replace, infer, or override anything the type’s renderer might display. The author’s declared parameters ride alongside the type’s standard fields. Cloud renderers compose the two surfaces; they do not choose between them.
  3. No reserved field names. No type reserves an input name. A pr_review workflow MAY name a field pull_request_url, pr, repo, severity, anything. The renderer does not look inside the input bag to populate its own UI; the type’s standard fields come from cloud-side trigger metadata (PR URL from the GitHub Action context, e.g.), not from inputs. If your workflow needs a value the renderer also displays, it is the author’s job to wire it through.
  4. Renderer scope. When a typed workflow declares inputs, the cloud run dashboard MAY display BOTH the input shape (key names + declared types, never values; see Telemetry shape) AND the type’s standard fields. The two panes are independent. A renderer MUST NOT fail or warn if a typed workflow declares inputs.
  5. No runtime coupling. The executor passes the resolved input bag to every node regardless of workflow_type. Type-specific cloud renderers do not introspect the input bag’s contents at runtime; they only see the declared shape via the telemetry surface.

If a future workflow type needs to require named input fields (e.g. a hypothetical scheduled_report type that requires a cadence field), the spec will name those fields explicitly and the validator will enforce them. As of v1, no type does.

id: release-notes
name: Release Notes
description: Draft release notes for the next tag
entry: discover
inputs:
since_tag:
type: string
description: Lower bound for the commit range. Default is the previous tag.
default: ""
until_tag:
type: string
description: Upper bound for the commit range.
default: HEAD
draft:
type: boolean
description: When true, post the notes as a draft instead of publishing.
default: true
audience:
type: string
description: Target audience for the notes.
enum: [drivers, dispatchers, engineering, all]
default: all
required_label:
type: string
required: true
description: Issue label to scope the changelog to.
nodes:
discover:
name: Discover changes
instruction: >-
Compare commits between input.since_tag and input.until_tag.
Scope to PRs labeled input.required_label. Group by area.
edges: []

CLI:

Terminal window
sweny workflow run release-notes.yml \
--input '{"since_tag":"v1.41.12","until_tag":"v1.41.13","required_label":"release"}'

GitHub Action (composite):

- uses: swenyai/sweny@v5
with:
workflow: .sweny/release-notes.yml
input: |
{
"since_tag": "${{ inputs.since_tag }}",
"until_tag": "${{ github.ref_name }}",
"required_label": "release"
}

Workflow authors reference inputs by name in node instructions and edge when conditions; the resolved bag is exposed to every node as input on the prompt context.

A conforming validator MUST enforce the following rules:

  1. Entry exists. The entry value MUST reference an existing key in nodes.
  2. Edge references. Every edge from and to value MUST reference an existing key in nodes.
  3. Reachability. All nodes MUST be reachable from entry via edges (BFS traversal from entry, following all outgoing edges regardless of conditions).
  4. Self-loops. An edge where from equals to MUST have max_iterations set. A self-loop without max_iterations is invalid.
  5. Unbounded cycles. A cycle in the graph where no edge in the cycle has max_iterations is invalid. A conforming validator MUST detect and reject unbounded cycles. The detection algorithm: remove all edges that have max_iterations (and all self-loops), then check the remaining subgraph for cycles using DFS.
  6. Skill references. All skill IDs referenced in node skills arrays SHOULD exist in the executor’s skill registry. Unknown skills are warnings, not errors.
  7. Inline skill constraint. Every entry in skills MUST provide at least one of instruction or mcp. An inline skill with neither is invalid.
CodeDescription
MISSING_ENTRYentry does not reference an existing node.
UNKNOWN_EDGE_SOURCEAn edge from references a non-existent node.
UNKNOWN_EDGE_TARGETAn edge to references a non-existent node.
UNREACHABLE_NODEA node is not reachable from entry.
SELF_LOOPA self-loop edge lacks max_iterations.
UNBOUNDED_CYCLEA cycle exists with no max_iterations guard.
UNKNOWN_SKILLA node references a skill not in the registry.
INVALID_INLINE_SKILLAn inline skill provides neither instruction nor mcp.

Inline skill definitions declared in the workflow’s skills block. These are scoped to the workflow and available to any node that references them by key.

FieldTypeRequiredDescription
namestringOPTIONALDisplay name. Defaults to the map key.
descriptionstringOPTIONALWhat this skill provides.
instructionstringOPTIONALNatural language expertise injected into the node prompt.
mcpMcpServerConfigOPTIONALExternal MCP server definition.

A valid SkillDefinition MUST provide at least one of instruction or mcp.

Inline skills cannot define tools (those require runtime code). They provide instruction-based expertise and/or external MCP server connections.

skills:
sre-rubric:
name: SRE Postmortem Rubric
instruction: |
Score each postmortem dimension 1-5:
- Completeness: Can you reconstruct the incident?
- Root Cause Depth: "Database was slow" = 1, specific PR + mechanism = 5
- Blamelessness: No individual names as responsible parties
- Action Items: Specific, assignable, measurable

The smallest valid workflow: one node, no edges, no routing.

id: hello
name: Hello World
entry: greet
nodes:
greet:
name: Greet
instruction: Say hello to the user.
edges: []

A workflow with conditional routing, multiple skills, structured output, and knowledge files.

id: triage
name: Alert Triage
description: Investigate a production alert and take action based on findings.
entry: gather
rules:
- ./coding-standards.md
context:
- ./ARCHITECTURE.md
skills:
triage-rubric:
name: Triage Severity Rubric
instruction: |
When assessing severity: critical = customer-facing outage,
high = degraded service, medium = internal impact, low = cosmetic.
nodes:
gather:
name: Gather Context
instruction: >-
Investigate the production alert. Pull error details, stack traces,
recent logs, and metrics. Check recent commits and deploys.
skills:
- github
- sentry
- datadog
investigate:
name: Root Cause Analysis
instruction: >-
Classify each issue found as novel or duplicate. Assess severity
and fix complexity. Output structured findings.
skills:
- github
- linear
- triage-rubric
output:
type: object
properties:
findings:
type: array
items:
type: object
properties:
title: { type: string }
severity: { type: string, enum: [critical, high, medium, low] }
is_duplicate: { type: boolean }
required: [title, severity, is_duplicate]
novel_count: { type: number }
highest_severity: { type: string }
required: [findings, novel_count, highest_severity]
create_issue:
name: Create Issues
instruction: Create issues for novel findings. Comment on duplicates.
skills:
- linear
- github
skip:
name: Skip
instruction: All findings were duplicates or low priority. Log and move on.
skills:
- linear
notify:
name: Notify Team
instruction: Send a notification summarizing the triage result.
skills:
- slack
edges:
- from: gather
to: investigate
- from: investigate
to: create_issue
when: novel_count is greater than 0 AND highest_severity is medium or higher
- from: investigate
to: skip
when: novel_count is 0, OR highest_severity is low
- from: create_issue
to: notify
- from: skip
to: notify

The canonical JSON Schema for validating Workflow documents is available at spec.sweny.ai/schemas/workflow.json.

Reference it from a YAML file:

# $schema: https://spec.sweny.ai/schemas/workflow.json
id: my-workflow
name: My Workflow
# ...