← Files aictrl.devARCHIVED FILE

reference/workflow.schema.json

11.7 KB · Sep 30, 2026 · 22:54 UTC

↓ Download file

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://aictrl.dev/schemas/workflow/v2/workflow.schema.json",
  "title": "aictrl Workflow (v2)",
  "description": "Declarative YAML/JSON workflow definition. V2 adds the portable inline task node: a node can define a pinned skill, prompt, parameters, and outputs in the file instead of relying on a pre-provisioned template. Existing template, workflow, loop, wait, manual, and user-input nodes remain supported. Layer 1 validates structure; layer 2 validates node inputs against referenced-template or in-file task parameters.",
  "type": "object",
  "required": ["schemaVersion", "name", "nodes"],
  "additionalProperties": false,
  "properties": {
    "schemaVersion": { "const": "aictrl/workflow/v2" },
    "name": { "type": "string", "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$", "description": "kebab-case; unique within org" },
    "label": { "type": "string" },
    "description": { "type": "string" },
    "category": { "type": "string" },
    "icon": { "type": "string", "description": "Lucide icon name" },
    "failureStrategy": { "enum": ["fail-fast", "continue-on-error"], "default": "fail-fast" },
    "parameters": { "type": "array", "items": { "$ref": "#/$defs/parameter" } },
    "nodes": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/node" } },
    "edges": { "type": "array", "items": { "$ref": "#/$defs/edge" } },
    "qualityGates": { "type": "array", "items": { "$ref": "#/$defs/qualityGate" } },
    "triggers": {
      "type": "array",
      "maxItems": 1,
      "description": "File-declared event triggers. Each trigger fires the workflow automatically when the event fires. Supports type:label (#3508) and type:comment (#3509); at most one trigger per workflow.",
      "items": { "$ref": "../v1/workflow.schema.json#/$defs/fileTrigger" }
    }
  },
  "$defs": {
    "parameter": { "$ref": "../v1/workflow.schema.json#/$defs/parameter" },
    "node": {
      "type": "object",
      "required": ["id", "type"],
      "additionalProperties": false,
      "properties": {
        "id": { "type": "string", "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$" },
        "label": { "type": "string" },
        "type": { "enum": ["template", "workflow", "loop", "wait", "manual", "user-input", "task"] },
        "template": { "type": "string", "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$", "description": "Portable template ref: the template's org-unique kebab `name` (e.g. \"inline-code-review\"). Resolved org-scoped to a templateId at load; layer-2 MUST confirm an active template with this name exists — see README." },
        "templateVersion": { "type": "string" },
        "workflow": { "type": "string", "pattern": "^[a-z0-9]+(-[a-z0-9]+)*$", "description": "Portable composite-workflow ref: the workflow's org-unique kebab `name` (e.g. \"release-pipeline\"). Resolved org-scoped at load." },
        "workflowVersion": { "type": "string" },
        "skill": { "type": "string", "pattern": "^[a-z0-9]+(-[a-z0-9]+)*(@[a-z0-9]+([.-][a-z0-9]+)*)?$", "description": "task: pinned skill ref — portable kebab `name` or `name@version` (the inline task's one external dependency). Version segments are separated by single `.`/`-` with no leading/trailing separator (so `code-review@1.` / `@1-` / `@1..2` are rejected). A `@version` pin makes the file reproducible; a bare `name` resolves to the org's active version at apply (a warning is logged). Skill resolution is org-scoped at apply-time — a file can never pull another tenant's skill (the tenancy boundary is enforced by the loader at runtime, not by this schema)." },
        "taskType": { "enum": ["code-review", "general"], "description": "task: task category and executor tool boundary. The enum expands only through released schema versions." },
        "prompt": { "type": "string", "maxLength": 16384, "description": "task: in-file system prompt — per-node parameterisation layered over the referenced skill (the skill is the procedure; the prompt is the per-node config)." },
        "outputs": { "type": "object", "propertyNames": { "pattern": "^[A-Za-z_][A-Za-z0-9_]*$" }, "additionalProperties": { "enum": ["string", "number", "boolean", "json"] }, "description": "task: declared output field name -> type. Declaring outputs in-file makes them referenceable by downstream `extract`/CEL with no DB round-trip (removes the #3296/#3425 friction)." },
        "inputs": { "type": "object", "additionalProperties": { "$ref": "#/$defs/inputMapping" } },
        "outputKey": { "type": "string" },
        "when": { "type": "string", "maxLength": 1024, "description": "CEL boolean expr; node is skipped when false" },
        "retry": { "$ref": "#/$defs/retry" },
        "maxIterations": { "type": "integer", "minimum": 1, "maximum": 25, "description": "loop: hard per-loop iteration cap (required for type=loop)" },
        "until": { "type": "string", "maxLength": 1024, "description": "loop: CEL exit condition, evaluated after each pass (do-while). Mutually exclusive with `while`." },
        "while": { "type": "string", "maxLength": 1024, "description": "loop: CEL continue condition, evaluated before each pass. Mutually exclusive with `until`." },
        "onMaxIterations": { "enum": ["fail", "continue", "warn"], "default": "fail" },
        "body": {
          "type": "object",
          "required": ["nodes"],
          "additionalProperties": false,
          "description": "Loop body subgraph. Per-loop cap is `maxIterations` (<=25); max nesting depth is 3. WORST CASE at the depth cap is the product of nested maxIterations (e.g. 25^3 = 15,625 leaf executions). A NORMATIVE global iteration budget (product of all nested maxIterations on any path <= 1000) is enforced at load by validateDag; runs exceeding it are rejected.",
          "properties": {
            "nodes": { "type": "array", "minItems": 1, "items": { "$ref": "#/$defs/node" } },
            "edges": { "type": "array", "items": { "$ref": "#/$defs/edge" } }
          }
        },
        "signalSource": { "type": "string", "maxLength": 256, "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,255}$", "description": "wait: opaque signal identifier matched by pure in-memory lookup. Charset forbids ':' and '/', so it can never be a URL/host the platform fetches (no SSRF)." },
        "timeoutMinutes": { "type": "integer", "minimum": 1 },
        "checklistItems": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "manual: items to complete" },
        "assignee": { "type": "string" },
        "parameters": { "type": "array", "items": { "$ref": "#/$defs/parameter" }, "description": "user-input: form fields; task: the inline task's declared parameters (layer-2 validates the node's `inputs` against these, exactly as a template's parameter schema is used for `template` nodes)." }
      },
      "allOf": [
        { "if": { "properties": { "type": { "const": "template" } }, "required": ["type"] },
          "then": { "required": ["template"], "not": { "anyOf": [
            { "required": ["workflow"] }, { "required": ["workflowVersion"] }, { "required": ["maxIterations"] }, { "required": ["until"] }, { "required": ["while"] }, { "required": ["onMaxIterations"] }, { "required": ["body"] }, { "required": ["signalSource"] }, { "required": ["timeoutMinutes"] }, { "required": ["checklistItems"] }, { "required": ["assignee"] }, { "required": ["parameters"] }, { "required": ["skill"] }, { "required": ["taskType"] }, { "required": ["prompt"] }, { "required": ["outputs"] } ] } } },
        { "if": { "properties": { "type": { "const": "workflow" } }, "required": ["type"] },
          "then": { "required": ["workflow"], "not": { "anyOf": [
            { "required": ["template"] }, { "required": ["templateVersion"] }, { "required": ["maxIterations"] }, { "required": ["until"] }, { "required": ["while"] }, { "required": ["onMaxIterations"] }, { "required": ["body"] }, { "required": ["signalSource"] }, { "required": ["timeoutMinutes"] }, { "required": ["checklistItems"] }, { "required": ["assignee"] }, { "required": ["parameters"] }, { "required": ["skill"] }, { "required": ["taskType"] }, { "required": ["prompt"] }, { "required": ["outputs"] } ] } } },
        { "if": { "properties": { "type": { "const": "loop" } }, "required": ["type"] },
          "then": { "required": ["maxIterations", "body"], "not": { "anyOf": [
            { "required": ["template"] }, { "required": ["templateVersion"] }, { "required": ["workflow"] }, { "required": ["workflowVersion"] }, { "required": ["signalSource"] }, { "required": ["timeoutMinutes"] }, { "required": ["checklistItems"] }, { "required": ["assignee"] }, { "required": ["parameters"] }, { "required": ["skill"] }, { "required": ["taskType"] }, { "required": ["prompt"] }, { "required": ["outputs"] }, { "required": ["until", "while"] } ] } } },
        { "if": { "properties": { "type": { "const": "wait" } }, "required": ["type"] },
          "then": { "required": ["signalSource"], "not": { "anyOf": [
            { "required": ["template"] }, { "required": ["templateVersion"] }, { "required": ["workflow"] }, { "required": ["workflowVersion"] }, { "required": ["maxIterations"] }, { "required": ["until"] }, { "required": ["while"] }, { "required": ["onMaxIterations"] }, { "required": ["body"] }, { "required": ["checklistItems"] }, { "required": ["assignee"] }, { "required": ["parameters"] }, { "required": ["skill"] }, { "required": ["taskType"] }, { "required": ["prompt"] }, { "required": ["outputs"] } ] } } },
        { "if": { "properties": { "type": { "const": "manual" } }, "required": ["type"] },
          "then": { "required": ["checklistItems"], "not": { "anyOf": [
            { "required": ["template"] }, { "required": ["templateVersion"] }, { "required": ["workflow"] }, { "required": ["workflowVersion"] }, { "required": ["maxIterations"] }, { "required": ["until"] }, { "required": ["while"] }, { "required": ["onMaxIterations"] }, { "required": ["body"] }, { "required": ["signalSource"] }, { "required": ["timeoutMinutes"] }, { "required": ["parameters"] }, { "required": ["skill"] }, { "required": ["taskType"] }, { "required": ["prompt"] }, { "required": ["outputs"] } ] } } },
        { "if": { "properties": { "type": { "const": "user-input" } }, "required": ["type"] },
          "then": { "required": ["parameters"], "not": { "anyOf": [
            { "required": ["template"] }, { "required": ["templateVersion"] }, { "required": ["workflow"] }, { "required": ["workflowVersion"] }, { "required": ["maxIterations"] }, { "required": ["until"] }, { "required": ["while"] }, { "required": ["onMaxIterations"] }, { "required": ["body"] }, { "required": ["signalSource"] }, { "required": ["timeoutMinutes"] }, { "required": ["checklistItems"] }, { "required": ["assignee"] }, { "required": ["skill"] }, { "required": ["taskType"] }, { "required": ["prompt"] }, { "required": ["outputs"] } ] } } },
        { "if": { "properties": { "type": { "const": "task" } }, "required": ["type"] },
          "then": { "required": ["skill"], "properties": { "timeoutMinutes": { "maximum": 60 } }, "not": { "anyOf": [
            { "required": ["template"] }, { "required": ["templateVersion"] }, { "required": ["workflow"] }, { "required": ["workflowVersion"] }, { "required": ["maxIterations"] }, { "required": ["until"] }, { "required": ["while"] }, { "required": ["onMaxIterations"] }, { "required": ["body"] }, { "required": ["signalSource"] }, { "required": ["checklistItems"] }, { "required": ["assignee"] } ] } } }
      ]
    },
    "inputMapping": { "$ref": "../v1/workflow.schema.json#/$defs/inputMapping" },
    "extract": { "$ref": "../v1/workflow.schema.json#/$defs/extract" },
    "edge": { "$ref": "../v1/workflow.schema.json#/$defs/edge" },
    "qualityGate": { "$ref": "../v1/workflow.schema.json#/$defs/qualityGate" },
    "retry": { "$ref": "../v1/workflow.schema.json#/$defs/retry" }
  }
}

SHA-256: f62ed9de9e44dc6895dcffe07dfdba4ec0499cee9b55a6638847123eb48377a5