{
  "name": "KG Process + Update (status state machine)",
  "goal": "Read entities from a KG list filtered by status, process each in a loop, and flip status when done so the same row is never re-processed.",
  "description": "Pattern 15b (processing half of the Source -> Process split) and pattern 12 (KG status as the API between workflows). Pair with `source-to-kg` as the producer. Loop processes one row at a time; after loop completion, `kg.update-rows` flips status to `processed` so the next run only sees what's still `new`.",
  "status": "draft",
  "context": {
    "executionInputConfig": {
      "title": "Process New Entities",
      "description": "Process entities flagged as `new` in the shared KG list.",
      "runCTALabel": "Process",
      "allowRegeneration": false,
      "fields": [
        { "name": "list_key", "label": "KG list key", "type": "text", "required": true }
      ]
    }
  },
  "steps": [
    {
      "id": "schedule",
      "type": "trigger",
      "name": "Daily process",
      "pipelineStepStartConditions": {
        "trigger": {
          "type": "schedule",
          "description": "Run daily at 09:00 — picks up everything sourced overnight.",
          "config": { "frequency": "daily", "time": "09:00" }
        }
      },
      "next": { "stepId": "read-new" }
    },
    {
      "id": "read-new",
      "type": "appAction",
      "name": "Read Rows with status: new",
      "app": { "id": "kg", "actionId": "kg.read-list", "source": "native" },
      "stepInputData": {
        "listKey": "{{input.list_key}}",
        "status": "new",
        "limit": "100"
      },
      "next": { "stepId": "process" }
    },
    {
      "id": "process",
      "type": "aiAction",
      "name": "Process Entity",
      "loopConfig": {
        "source": "executionContent",
        "config": { "stepId": "read-new", "field": "listEntries" },
        "ItemAlias": "row"
      },
      "pipelineStepPrompt": {
        "template": "Process this entity. Replace this prompt with the real processing logic for your domain (enrich, score, classify, draft, etc.).\n\nEntity: {{row}}\n\nReturn the rowId verbatim plus any fields you want merged onto the KG row.",
        "responseStructure": {
          "rowId": "string — copy {{row.id}} verbatim so the update step can target this exact row",
          "result": "object — fields to merge into the KG row (scores, decisions, derived data)"
        }
      },
      "creditCost": 5,
      "next": { "stepId": "mark-processed" }
    },
    {
      "id": "mark-processed",
      "type": "appAction",
      "name": "Flip status -> processed",
      "app": { "id": "kg", "actionId": "kg.update-rows", "source": "native" },
      "entryConditions": {
        "onCriteriaFail": "wait",
        "conditionText": "Wait for the processing loop to finish before flipping status.",
        "criteria": [
          { "type": "loop_completion", "stepId": "process", "operator": "==", "value": true }
        ]
      },
      "stepInputData": {
        "listKey": "{{input.list_key}}",
        "rowIds": "{{steps.process.rowIds}}",
        "fieldUpdates": "{{steps.process.results}}",
        "status": "processed"
      },
      "next": { "stepId": "done" }
    },
    {
      "id": "done",
      "type": "milestone",
      "name": "Processing Done"
    }
  ]
}
