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.

Field Type Required Default Description
id string REQUIRED — Unique identifier for this workflow. MUST be non-empty.
name string REQUIRED — Human-readable name. MUST be non-empty.
description string OPTIONAL "" Purpose of the workflow.
entry string REQUIRED — Node ID where execution begins. MUST reference a key in nodes.
nodes Record<string, Node> REQUIRED — Map of node ID to Node definition. Keys are referenced by entry and edge from/to fields.
edges Edge[] REQUIRED — Directed edges defining execution flow and routing conditions.
skills Record<string, SkillDefinition> OPTIONAL {} Inline skill definitions scoped to this workflow.
rules Source[] OPTIONAL [] Directives prepended to every node’s instruction. Organization-wide policies, coding standards.
context Source[] OPTIONAL [] Background knowledge prepended to every node’s instruction. Architecture docs, playbooks.
context_mode string (enum) OPTIONAL bounded What prior results each node’s prompt receives: bounded or full. See What a node receives.
spec_version string OPTIONAL "1" Spec version this file was written against: a positive integer string. See Spec version.
workflow_type string (enum) OPTIONAL generic Workflow type discriminator. Routes runs to a type-specific renderer. See Workflow type.
model string OPTIONAL impl‑defined Default execution model for every node. Overridable per-node. Free-text passthrough. See Nodes — Model Selection.
budget Budget OPTIONAL none Token and cost ceiling for the whole run and for every node. See Spend Budgets.
decider Decider OPTIONAL none Thresholds (min_confidence, min_margin) for nodes that set route_by: decider. The provider comes from operator config. See Decision models.
inputs Record<string, InputField> OPTIONAL — Declared per-run input contract. The CLI validates --input against this and applies defaults. See Inputs.
permissions Permissions OPTIONAL write Default and ceiling for every node’s permissions.
safe_outputs SafeOutputsPolicy OPTIONAL none Run-wide limits for safe outputs. See Safe Outputs Policy.

spec_version is a positive integer string ("1", "2", …). A file that omits it is version 1.

  • Older than the implementation supports: the implementation MUST migrate the file to the current version in memory before validating it, and SHOULD warn once, naming the file’s version and how to upgrade it. Migrations are applied one version at a time (v1 to v2, then v2 to v3).
  • Newer than the implementation supports: the implementation MUST refuse the file with an error telling the user to upgrade the implementation. It MUST NOT guess.
  • Invalid (not a positive integer string): the file is rejected.

In the reference implementation, sweny workflow upgrade <file> applies the migrations and rewrites the file with spec_version set. --dry-run reports the change without writing. YAML comments, blank lines and key order are kept for everything a migration does not touch; comments attached to values a migration replaces or removes are not.

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.

Value What it produces Typical metrics
pr_review A PR comment with risks and suggestions Caught / Calibration / Missed
e2e_test Pass-fail per test, screenshots, traces Flake rate, step failure heatmap, run duration
content_generation Text or JSON artifacts (e.g. changelog drafts) Acceptance rate, regen rate, edit distance
monitor A delta report (e.g. weekly regulatory monitor) Coverage, novel-finding rate, staleness
data_sync Records processed counts, errors, latency Throughput, error rate, drift
generic Anything else Run 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:

Field Type Required Description
type string (enum) REQUIRED One of string, number, boolean, string[].
description string OPTIONAL Human-readable purpose. Shown in CLI errors and cloud renderers.
required boolean OPTIONAL When true, the caller MUST provide a value. Default: false.
default matches type OPTIONAL Value applied when the caller omits the field. Type-checked at parse time against type.
enum array OPTIONAL Optional 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.
Code Description
MISSING_ENTRY entry does not reference an existing node.
UNKNOWN_EDGE_SOURCE An edge from references a non-existent node.
UNKNOWN_EDGE_TARGET An edge to references a non-existent node.
UNREACHABLE_NODE A node is not reachable from entry.
SELF_LOOP A self-loop edge lacks max_iterations.
UNBOUNDED_CYCLE A cycle exists with no max_iterations guard.
EDGE_ITERATIONS_EXCEEDED An edge’s max_iterations is above 100.
RETRY_MAX_EXCEEDED A node’s retry.max is above 10.
UNKNOWN_SKILL A node references a skill not in the registry.
INVALID_INLINE_SKILL An inline skill provides neither instruction nor mcp.
PERMISSION_CEILING A node declares permissions: write under a workflow read.
AGENT_ENV_READ_ONLY A node whose permissions resolve to read declares agent_env.
BUDGET_CEILING A node’s budget is above the workflow’s budget for the same unit.
OUTPUT_NOT_ALLOWED A node declares an output type outside safe_outputs.allow.
DUPLICATE_OUTPUT A node declares the same output type twice.
UNSUPPORTED_OUTPUT An output’s via names a skill that cannot apply its type, or a number pin is on an output other than comment or label.
INVALID_WHEN_EXPRESSION An edge’s when expression does not parse or names an undeclared field or a node that cannot run first (see Expressions).
MIXED_EDGE_CONDITIONS A node’s conditional out-edges mix expressions and natural language.
DUPLICATE_EDGE Two edges share the same from and to. Merge them into one edge whose condition covers both (OR).

safe_outputs sets the ceiling and run-wide limits for every node’s outputs.

Field Type Required Description
allow (comment | issue | pr | label | issue_state)[] OPTIONAL Output types any node may declare. A node declaring another type is invalid (OUTPUT_NOT_ALLOWED).
max integer 1..100 OPTIONAL Total writes per run across all nodes.
staged boolean OPTIONAL Preview every write and apply none.
trusted_actors string[] OPTIONAL GitHub logins whose runs may write.
trusted_associations string[] OPTIONAL Author associations (OWNER, MEMBER, COLLABORATOR, …) of the triggering event whose runs may write.
screen boolean OPTIONAL After every deterministic check, one model call may veto the writes. It can never authorize one.

When trusted_actors or trusted_associations is set, a run whose actor matches neither, or whose actor is unknown, writes nothing. The actor is GITHUB_ACTOR, and the association comes from the event payload (GITHUB_EVENT_PATH: the comment, review, issue or pull request author).

permissions: read
safe_outputs:
allow: [comment, label]
max: 2
trusted_associations: [OWNER, MEMBER, COLLABORATOR]

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.

Field Type Required Description
name string OPTIONAL Display name. Defaults to the map key.
description string OPTIONAL What this skill provides.
instruction string OPTIONAL Natural language expertise injected into the node prompt.
mcp McpServerConfig OPTIONAL External 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
# ...