{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/aimeat-io/json-schema/aimeat-io-workflow-run-schema.json",
  "title": "WorkflowRun",
  "x-generated": "2026-11-01",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/aimeat-io-openapi.json#/components/schemas/WorkflowRun",
  "type": "object",
  "properties": {
    "runId": {
      "type": "string"
    },
    "workflowId": {
      "type": "string"
    },
    "status": {
      "type": "string",
      "enum": [
        "running",
        "waiting-step",
        "red",
        "partial",
        "done",
        "cancelled",
        "stopped",
        "refused"
      ],
      "description": "`stopped`: the node ended the run at its cost cap (maxCostUsd); `costCap` and `reason` say why. `refused`: the workflow's own trigger found the principal that saved it gone, or short of a permission the steps need, so nothing ran; `refusal` and `reason` say which. A run already going is `refused` too when, before a step is dispatched, the principal it answers to (`principal`) is gone or no longer holds a permission that step costs; then `refusal.stoppedBefore` names the step, and the steps not yet finished are `skipped`."
    },
    "principal": {
      "type": "object",
      "description": "The principal the run answers to, asked again before every step is dispatched: the principal that started it, or for a run the trigger started, the workflow's saver. Absent on a run started before 2026-10-20, whose saver stands in.",
      "properties": {
        "kind": {
          "type": "string",
          "enum": [
            "owner",
            "agent",
            "ecosystem",
            "app"
          ]
        },
        "id": {
          "type": "string"
        }
      }
    },
    "reason": {
      "type": "string",
      "description": "Why the node ended the run itself, or did not start it, in words."
    },
    "refusal": {
      "type": "object",
      "description": "On a `refused` run. One record stands for every refused start until the workflow runs again; `attempts` counts them. The owner is told once, with \"Run as me\" (POST /v1/workflows/{id}/runs/{runId}/run-as-owner) and a link to the saver's permissions.",
      "properties": {
        "saver": {
          "type": "object",
          "properties": {
            "kind": {
              "type": "string",
              "enum": [
                "owner",
                "agent",
                "ecosystem",
                "app"
              ]
            },
            "id": {
              "type": "string"
            }
          }
        },
        "saverName": {
          "type": "string"
        },
        "missing": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "description": "The permissions the saver lacks; empty when it is gone."
        },
        "gone": {
          "type": "boolean"
        },
        "attempts": {
          "type": "integer"
        },
        "lastAttemptAt": {
          "type": "string",
          "format": "date-time"
        },
        "ranAsOwner": {
          "type": "object",
          "description": "The run the owner started from this refusal, on their own authority.",
          "properties": {
            "runId": {
              "type": "string"
            },
            "at": {
              "type": "string",
              "format": "date-time"
            }
          }
        },
        "stoppedBefore": {
          "type": "string",
          "description": "Set when the run had started and was refused in flight: the step that was not dispatched."
        }
      }
    },
    "costCap": {
      "type": "object",
      "description": "On a `stopped` run: the cap, what the run had spent on AI (US dollars), and the ai step that did not start. `neededUsd` is that step's estimate, present when the run had not spent the cap and the estimate did not fit in what was left.",
      "properties": {
        "capUsd": {
          "type": "number"
        },
        "spentUsd": {
          "type": "number"
        },
        "stoppedBefore": {
          "type": "string"
        },
        "neededUsd": {
          "type": "number"
        }
      }
    },
    "signalCostUsd": {
      "type": "number",
      "description": "What the node's model cost judging this run's `llm` signals, in US dollars, as the node recorded it. It counts toward maxCostUsd beside each step's own `costUsd`."
    },
    "mode": {
      "type": "string",
      "enum": [
        "full-live",
        "full-sandbox",
        "signals-only"
      ]
    },
    "vars": {
      "type": "object",
      "additionalProperties": {
        "type": "string"
      }
    },
    "steps": {
      "type": "object",
      "additionalProperties": {
        "type": "object",
        "properties": {
          "state": {
            "type": "string",
            "enum": [
              "pending",
              "input-red",
              "dispatched",
              "green",
              "output-red",
              "timed-out",
              "skipped",
              "agent-offline",
              "waiting-human"
            ]
          },
          "attempt": {
            "type": "integer"
          },
          "costUsd": {
            "type": "number",
            "description": "What this step's own model calls cost, in US dollars, as the node recorded them (an ai step; a retry adds to it, and so does a call that answered after the step was moved on)."
          },
          "attemptMaxUsd": {
            "type": "number",
            "description": "The most one attempt of this ai step cost, in US dollars: one model call with its re-asks for JSON. Later runs read their estimate from it."
          },
          "estimateUsd": {
            "type": "number",
            "description": "Under maxCostUsd: what one attempt of this ai step is expected to cost, set when the run starts. The most one attempt cost in the workflow's last ten finished runs that ran it with the same action; absent when there is no such run."
          },
          "openCalls": {
            "type": "array",
            "description": "The calls of this ai, extension or datapackage step that have started and not yet answered, one per attempt, each with what the node set aside for it from the maxCostUsd cap before it started (for an ai step under the cap, the step's estimateUsd, else its share of the cap nobody held; 0 for any other call). A call keeps its hold until it answers, whatever became of the step meanwhile. While the call of the step's current attempt is open, the watchdog does not end the step by its output: only that call's answer or the step's timeout ends it. A restart of the node clears them on a run still in flight, because the calls ended with it.",
            "items": {
              "type": "object",
              "properties": {
                "attempt": {
                  "type": "integer"
                },
                "reservedUsd": {
                  "type": "number"
                }
              }
            }
          },
          "taskIds": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "reads": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "writes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "human": {
            "type": "object",
            "description": "Human-input bookkeeping: the {var}-templated question pinned at ask time, and the answer once given. `answer.pick` is the flat first pick (json_field-friendly); `answer.by` is the answering principal, or \"timeout-default\".",
            "properties": {
              "question": {
                "type": "object",
                "properties": {
                  "header": {
                    "type": "string"
                  },
                  "prompt": {
                    "type": "string"
                  },
                  "options": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "string"
                        },
                        "label": {
                          "type": "string"
                        }
                      }
                    }
                  },
                  "multiSelect": {
                    "type": "boolean"
                  },
                  "allowOther": {
                    "type": "boolean"
                  }
                }
              },
              "askedAt": {
                "type": "string",
                "format": "date-time"
              },
              "answeredAt": {
                "type": "string",
                "format": "date-time"
              },
              "answer": {
                "type": "object",
                "properties": {
                  "picks": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  },
                  "pick": {
                    "type": "string"
                  },
                  "other": {
                    "type": "string"
                  },
                  "by": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "progress": {
            "type": "object",
            "description": "Live fill progress for a dispatched step, sampled by the watchdog from the success signal's count_nonempty leaves. A rising count means the crew is still filling keys (in-progress); the step only times out after timeout_min with increasing=false. Absent for signals with no countable leaf. Surface as \"leaves count/min\".",
            "properties": {
              "count": {
                "type": "integer"
              },
              "min": {
                "type": "integer"
              },
              "increasing": {
                "type": "boolean"
              },
              "lastProgressAt": {
                "type": "string",
                "format": "date-time"
              }
            }
          }
        }
      }
    },
    "startedAt": {
      "type": "string",
      "format": "date-time"
    },
    "endedAt": {
      "type": "string",
      "format": "date-time"
    }
  }
}