{
  "name": "AA Factory 02 - Ensure Model Ready",
  "nodes": [
    {
      "parameters": {},
      "id": "f3728b44-756e-4127-9686-b39a47b4d5dc",
      "name": "Sub-workflow Input",
      "type": "n8n-nodes-base.executeWorkflowTrigger",
      "typeVersion": 1,
      "position": [
        -1936,
        -112
      ]
    },
    {
      "parameters": {
        "jsCode": "const input = $json ?? {};\nconst now = new Date().toISOString();\nconst obs = input.observability ?? {};\nconst trace = Array.isArray(obs.trace) ? [...obs.trace] : [];\nconst startedAt = obs.stage_started_at ?? obs.started_at ?? now;\nconst durationMs = Math.max(0, Date.parse(now) - Date.parse(startedAt));\nconst event = {\n  timestamp: now,\n  correlation_id: obs.correlation_id ?? input.correlation_id ?? null,\n  workflow: $workflow.name,\n  execution_id: $execution.id,\n  stage: 'model-readiness',\n  status: 'started',\n  duration_ms: durationMs,\n  ...({}),\n};\ntrace.push(event);\nconsole.log(JSON.stringify({event:'autonomous_agent_workflow_stage', ...event}));\nreturn [{json:{...input, observability:{...obs, stage_started_at:now, trace}, last_event:event}}];"
      },
      "id": "0a161206-7d5b-4eeb-a6f8-7f1a925b1254",
      "name": "Start Model Readiness Trace",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -1744,
        -112
      ]
    },
    {
      "parameters": {
        "operation": "get",
        "modelName": "={{ $json.blueprint.model_policy.model }}"
      },
      "id": "5f6b4718-b72e-4a71-8fa0-dcf6c933f06e",
      "name": "Inspect Required Model",
      "type": "n8n-nodes-autonomous-agent.autonomousAgentLlmModel",
      "typeVersion": 1,
      "position": [
        -1520,
        -112
      ],
      "credentials": {
        "autonomousAgentApi": {
          "name": "<your credential>"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "const rawResponse = $json ?? {};\n\n// Support:\n// 1. Direct array: [{ model, details }]\n// 2. Wrapped array: { data: [{ model, details }] }\n// 3. Direct object: { model, details }\nconst modelRecord = Array.isArray(rawResponse)\n  ? rawResponse[0]\n  : Array.isArray(rawResponse.data)\n    ? rawResponse.data[0]\n    : rawResponse;\n\nconst inspectedModel =\n  modelRecord &&\n  typeof modelRecord === 'object' &&\n  modelRecord.model != null &&\n  modelRecord.details != null\n    ? modelRecord\n    : null;\n\nconst pullError =\n  rawResponse.error ??\n  rawResponse.details?.error ??\n  modelRecord?.error ??\n  modelRecord?.details?.error ??\n  null;\n\nconst pullMessage = String(\n  rawResponse.message ??\n  rawResponse.detail ??\n  pullError?.message ??\n  pullError?.detail ??\n  ''\n);\n\nconst timeoutObserved =\n  /timeout|timed out|30000ms exceeded/i.test(pullMessage);\n\nconst requestedModel =\n  $('Start Model Readiness Trace')\n    .item\n    .json\n    .blueprint\n    .model_policy\n    .model;\n\nreturn [{\n  json: {\n    model: inspectedModel?.model ?? requestedModel,\n\n    // True only when both model and details are present.\n    modelInstalled: inspectedModel !== null,\n\n    provider: inspectedModel?.provider ?? null,\n    modelDetails: inspectedModel?.details ?? null,\n\n    pollAttempt: 0,\n    maximumPollAttempts: 60,\n    pollIntervalSeconds: 15,\n\n    pullRequestTimedOut: timeoutObserved,\n    pullRequestMessage: pullMessage,\n  },\n}];"
      },
      "id": "2d7d873a-92b1-4acb-abcd-becd691edd5a",
      "name": "Normalize Model Inspection",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -1296,
        -112
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "1b1e09ab-2783-4e6d-a6bb-8da1385851b3",
              "leftValue": "={{ $json.modelInstalled }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "false",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "66182bde-0c12-477d-bc32-061a738e1175",
      "name": "Model Installed?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        -1088,
        -112
      ]
    },
    {
      "parameters": {
        "operation": "pull",
        "model": "={{ $json.model }}"
      },
      "id": "6cd08b57-37c6-489b-adb2-f3ce98e32733",
      "name": "Pull Required Model",
      "type": "n8n-nodes-autonomous-agent.autonomousAgentLlmModel",
      "typeVersion": 1,
      "position": [
        -864,
        16
      ],
      "retryOnFail": false,
      "notesInFlow": true,
      "credentials": {
        "autonomousAgentApi": {
          "name": "<your credential>"
        }
      },
      "onError": "continueRegularOutput",
      "notes": "A 30-second client timeout does not necessarily mean the Ollama pull stopped. Continue into bounded inspection polling and only fail when the model is still unavailable after the configured polling limit."
    },
    {
      "parameters": {
        "jsCode": "const rawResponse = $json ?? {};\n\n// Supports:\n// 1. [{ provider, model, status, result }]\n// 2. { data: [{ provider, model, status, result }] }\n// 3. { provider, model, status, result }\nconst modelRecord = Array.isArray(rawResponse)\n  ? rawResponse[0]\n  : Array.isArray(rawResponse.data)\n    ? rawResponse.data[0]\n    : rawResponse;\n\nconst pullError =\n  rawResponse.error ??\n  rawResponse.details?.error ??\n  modelRecord?.error ??\n  modelRecord?.details?.error ??\n  null;\n\nconst pullMessage = String(\n  rawResponse.message ??\n  rawResponse.detail ??\n  modelRecord?.message ??\n  modelRecord?.detail ??\n  pullError?.message ??\n  pullError?.detail ??\n  ''\n);\n\nconst timeoutObserved =\n  /timeout|timed out|30000ms exceeded/i.test(pullMessage);\n\nconst requestedModel =\n  $('Start Model Readiness Trace')\n    .item\n    .json\n    .blueprint\n    .model_policy\n    .model;\n\nconst hasModel =\n  typeof modelRecord?.model === 'string' &&\n  modelRecord.model.trim().length > 0;\n\nconst modelAvailable =\n  hasModel &&\n  (\n    modelRecord.status === 'available' ||\n    modelRecord.result?.status === 'success'\n  );\n\nreturn [{\n  json: {\n    model: hasModel\n      ? modelRecord.model\n      : requestedModel,\n\n    provider: modelRecord?.provider ?? null,\n    modelStatus: modelRecord?.status ?? null,\n    modelAvailable,\n\n    usedByPolicies: Array.isArray(modelRecord?.used_by_policies)\n      ? modelRecord.used_by_policies\n      : [],\n\n    nextAction: modelRecord?.next_action ?? null,\n    resultStatus: modelRecord?.result?.status ?? null,\n\n    pollAttempt: 0,\n    maximumPollAttempts: 60,\n    pollIntervalSeconds: 15,\n\n    pullRequestTimedOut: timeoutObserved,\n    pullRequestMessage: pullMessage,\n  },\n}];"
      },
      "id": "04838327-1f3a-4b16-b818-9d1040374320",
      "name": "Initialize Model Poll",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        -640,
        16
      ]
    },
    {
      "parameters": {
        "amount": 15
      },
      "id": "1336086f-7b1f-401c-9615-6be82b9b5a2a",
      "name": "Wait Before Model Recheck",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1.1,
      "position": [
        -416,
        16
      ]
    },
    {
      "parameters": {
        "operation": "get",
        "modelName": "={{ $json.model }}"
      },
      "id": "813d3ec0-ba06-467e-b896-8dab25b452db",
      "name": "Inspect Pulled Model",
      "type": "n8n-nodes-autonomous-agent.autonomousAgentLlmModel",
      "typeVersion": 1,
      "position": [
        -208,
        16
      ],
      "retryOnFail": false,
      "credentials": {
        "autonomousAgentApi": {
          "name": "<your credential>"
        }
      },
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "jsCode": "const rawResponse = $json ?? {};\nlet previousPoll = {};\ntry { previousPoll = $('Increment Model Poll').item.json; } catch { previousPoll = $('Initialize Model Poll').item.json; }\n\n// Support:\n// 1. Direct array: [{ model, details }]\n// 2. Wrapped array: { data: [{ model, details }] }\n// 3. Direct object: { model, details }\nconst modelRecord = Array.isArray(rawResponse)\n  ? rawResponse[0]\n  : Array.isArray(rawResponse.data)\n    ? rawResponse.data[0]\n    : rawResponse;\n\nconst inspectedModel =\n  modelRecord &&\n  typeof modelRecord === 'object' &&\n  modelRecord.model != null &&\n  modelRecord.details != null\n    ? modelRecord\n    : null;\n\nconst pullError =\n  rawResponse.error ??\n  rawResponse.details?.error ??\n  modelRecord?.error ??\n  modelRecord?.details?.error ??\n  null;\n\nconst pullMessage = String(\n  rawResponse.message ??\n  rawResponse.detail ??\n  pullError?.message ??\n  pullError?.detail ??\n  ''\n);\n\nconst timeoutObserved =\n  /timeout|timed out|30000ms exceeded/i.test(pullMessage);\n\nconst requestedModel =\n  $('Start Model Readiness Trace')\n    .item\n    .json\n    .blueprint\n    .model_policy\n    .model;\n\nreturn [{\n  json: {\n    model: inspectedModel?.model ?? requestedModel,\n\n    // True only when both model and details are present.\n    modelInstalled: inspectedModel !== null,\n\n    provider: inspectedModel?.provider ?? null,\n    modelDetails: inspectedModel?.details ?? null,\n\n    pollAttempt: Number(previousPoll.pollAttempt ?? 0),\n    maximumPollAttempts: Number(previousPoll.maximumPollAttempts ?? 60),\n    pollIntervalSeconds: Number(previousPoll.pollIntervalSeconds ?? 15),\n\n    pullRequestTimedOut: timeoutObserved,\n    pullRequestMessage: pullMessage,\n  },\n}];"
      },
      "id": "0a49d856-2ea2-4e23-bcc8-e9cfb5a5e52c",
      "name": "Normalize Pulled Model Inspection",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        32,
        16
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "5d44ceae-aec1-480e-a472-ebd7ef593d72",
              "leftValue": "={{ $json.modelInstalled }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "896fedf2-2b0a-4cd9-8dd7-596207c203b2",
      "name": "Model Pull Complete?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        240,
        16
      ]
    },
    {
      "parameters": {
        "jsCode": "return [{\n  json: {\n    ...$json,\n    pollAttempt: Number($json.pollAttempt ?? 0) + 1,\n    maximumPollAttempts: Number($json.maximumPollAttempts ?? 60),\n    pollIntervalSeconds: Number($json.pollIntervalSeconds ?? 10),\n  },\n}];"
      },
      "id": "debf7a48-0efa-4e19-81c9-151cfc8b04cf",
      "name": "Increment Model Poll",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        464,
        128
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict",
            "version": 2
          },
          "conditions": [
            {
              "id": "df92feee-63f6-4589-b51c-3d5ce558c691",
              "leftValue": "={{ $json.pollAttempt >= $json.maximumPollAttempts }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "498d9d2e-d32b-42f5-b225-09c65cb9ed06",
      "name": "Model Poll Limit Reached?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        688,
        128
      ]
    },
    {
      "parameters": {
        "jsCode": "throw new Error(\n  `Ollama model ${$json.model} was not installed after ` +\n  `${$json.pollAttempt} polling attempts`\n);"
      },
      "id": "3b0e2a9b-9a3c-4f64-a024-529eeef58a4c",
      "name": "Model Pull Timed Out",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        912,
        128
      ]
    },
    {
      "parameters": {
        "jsCode": "const state=$json; const parent=$('Sub-workflow Input').item.json; const now=new Date().toISOString(); const obs=parent.observability??{}; const trace=Array.isArray(obs.trace)?[...obs.trace]:[]; const startedAt=obs.stage_started_at??obs.started_at??now; const event={timestamp:now,correlation_id:obs.correlation_id,workflow:$workflow.name,execution_id:$execution.id,stage:'model-readiness',status:'completed',duration_ms:Math.max(0,Date.parse(now)-Date.parse(startedAt)),model:state.model??parent.blueprint?.model_policy?.model,poll_attempts:Number(state.pollAttempt??0),pull_timeout_observed:Boolean(state.pullRequestTimedOut)}; trace.push(event); console.log(JSON.stringify({event:'autonomous_agent_workflow_stage',...event})); return [{json:{...parent,model_readiness:state,observability:{...obs,stage_started_at:now,trace},last_event:event}}];"
      },
      "id": "8c8a2d0f-eb26-4670-84bc-569cbd9fb723",
      "name": "Model Ready",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        480,
        -144
      ]
    }
  ],
  "connections": {
    "Sub-workflow Input": {
      "main": [
        [
          {
            "node": "Start Model Readiness Trace",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Start Model Readiness Trace": {
      "main": [
        [
          {
            "node": "Inspect Required Model",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Inspect Required Model": {
      "main": [
        [
          {
            "node": "Normalize Model Inspection",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Model Inspection": {
      "main": [
        [
          {
            "node": "Model Installed?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Model Installed?": {
      "main": [
        [
          {
            "node": "Model Ready",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Pull Required Model",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Pull Required Model": {
      "main": [
        [
          {
            "node": "Initialize Model Poll",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Initialize Model Poll": {
      "main": [
        [
          {
            "node": "Wait Before Model Recheck",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait Before Model Recheck": {
      "main": [
        [
          {
            "node": "Inspect Pulled Model",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Inspect Pulled Model": {
      "main": [
        [
          {
            "node": "Normalize Pulled Model Inspection",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize Pulled Model Inspection": {
      "main": [
        [
          {
            "node": "Model Pull Complete?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Model Pull Complete?": {
      "main": [
        [
          {
            "node": "Model Ready",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Increment Model Poll",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Increment Model Poll": {
      "main": [
        [
          {
            "node": "Model Poll Limit Reached?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Model Poll Limit Reached?": {
      "main": [
        [
          {
            "node": "Model Pull Timed Out",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Wait Before Model Recheck",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": true,
  "settings": {
    "executionOrder": "v1",
    "binaryMode": "separate",
    "availableInMCP": false
  },
  "versionId": "b16b796e-f88f-46d4-9648-28b53bea6dde",
  "nodeGroups": [],
  "id": "TwazUNKFroOI0ENW",
  "tags": [
    {
      "name": "factory",
      "id": "RafL6i7R2YphAkeq",
      "updatedAt": "2026-07-26T11:35:57.670Z",
      "createdAt": "2026-07-26T11:35:57.670Z"
    }
  ]
}