{
  "name": "AA Factory 01 - Plan Blueprint",
  "nodes": [
    {
      "parameters": {},
      "id": "86bc9e39-061a-4b9b-8c1d-730ddeaa16dd",
      "name": "Sub-workflow Input",
      "type": "n8n-nodes-base.executeWorkflowTrigger",
      "typeVersion": 1,
      "position": [
        0,
        0
      ]
    },
    {
      "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: 'configuration-planning',\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": "fe879870-c6b2-4103-9b9b-78f6a88781da",
      "name": "Start Planning Trace",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        224,
        0
      ]
    },
    {
      "parameters": {
        "task": "=You are the configuration architect for an Autonomous Agent Platform integrated with n8n. Convert the developer request below into one deployable configuration blueprint. Return exactly one JSON object containing model_policy, model_capability, execution, and optional tool at the root level. Do not wrap the result in an output property. Do not use Markdown or code fences. Select a suitable model policy and purpose; define strict JSON Schemas for the model capability and final execution. Every schema must be a complete JSON Schema object using type, properties, required, and additionalProperties where applicable; do not return a bare property map. For model capabilities use agent=model-agent and agent_version=1.0.0. Use a timeout_seconds value of at least 300 for generated model capabilities. Only when a new external/custom tool is actually required, define one custom MCP server, MCP tool registration, and matching tool capability. Prefer existing immutable core tools such as web-search, page-extract, ocr, workspace-list, workspace-read, workspace-write, and http-request instead of creating duplicates. workspace-write and python-execute are approval-sensitive. The tool registry itself is read-only, so custom tools must be represented as MCP registrations. Ensure all resource names are lowercase kebab-case, model_purpose is included in allowed_purposes, and the final execution allowed capabilities can be derived from the generated capability names. Developer request and defaults: {{ JSON.stringify($json) }}",
        "contextInputMode": "json",
        "contextJson": "={{ JSON.stringify({source:'n8n-workflow-factory', developerRequest:$json.request.developer_request, defaults:$json.defaults}) }}",
        "includeDesiredOutput": true,
        "desiredOutputSchema": "={\"type\":\"object\",\"additionalProperties\":false,\"required\":[\"model_policy\",\"model_capability\",\"execution\"],\"properties\":{\"model_policy\":{\"type\":\"object\",\"additionalProperties\":false,\"required\":[\"name\",\"model\",\"provider\",\"input_modalities\",\"maximum_input_characters\",\"maximum_output_tokens\",\"maximum_cost_units\",\"allowed_purposes\"],\"properties\":{\"name\":{\"type\":\"string\"},\"model\":{\"type\":\"string\"},\"provider\":{\"type\":\"string\",\"enum\":[\"ollama\"]},\"input_modalities\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"enum\":[\"text\",\"image\"]},\"minItems\":1},\"maximum_input_characters\":{\"type\":\"integer\",\"minimum\":1,\"maximum\":500000},\"maximum_output_tokens\":{\"type\":\"integer\",\"minimum\":1,\"maximum\":32768},\"maximum_cost_units\":{\"type\":\"integer\",\"minimum\":1,\"maximum\":1000000},\"allowed_purposes\":{\"type\":\"array\",\"items\":{\"type\":\"string\"},\"minItems\":1}}},\"model_capability\":{\"type\":\"object\",\"additionalProperties\":false,\"required\":[\"name\",\"description\",\"agent\",\"agent_version\",\"model_purpose\",\"input_modalities\",\"input_schema\",\"output_schema\",\"timeout_seconds\",\"maximum_concurrency\"],\"properties\":{\"name\":{\"type\":\"string\"},\"description\":{\"type\":\"string\"},\"agent\":{\"type\":\"string\"},\"agent_version\":{\"type\":\"string\"},\"model_purpose\":{\"type\":\"string\"},\"input_modalities\":{\"type\":\"array\",\"items\":{\"type\":\"string\",\"enum\":[\"text\",\"image\"]}},\"input_schema\":{\"type\":\"object\"},\"output_schema\":{\"type\":\"object\"},\"timeout_seconds\":{\"type\":\"integer\"},\"maximum_concurrency\":{\"type\":\"integer\"}}},\"tool\":{\"type\":[\"object\",\"null\"],\"additionalProperties\":false,\"required\":[\"server\",\"registration\",\"capability\"],\"properties\":{\"server\":{\"type\":\"object\",\"required\":[\"name\",\"base_url\"],\"properties\":{\"name\":{\"type\":\"string\"},\"base_url\":{\"type\":\"string\"},\"timeout_seconds\":{\"type\":\"integer\"},\"maximum_result_bytes\":{\"type\":\"integer\"}}},\"registration\":{\"type\":\"object\",\"required\":[\"name\",\"remote_tool_name\",\"description\",\"input_schema\",\"output_schema\",\"side_effect\",\"approval_required\"],\"properties\":{\"name\":{\"type\":\"string\"},\"remote_tool_name\":{\"type\":\"string\"},\"description\":{\"type\":\"string\"},\"adapter_type\":{\"type\":\"string\"},\"adapter_config\":{\"type\":\"object\"},\"input_schema\":{\"type\":\"object\"},\"output_schema\":{\"type\":\"object\"},\"side_effect\":{\"type\":\"string\"},\"approval_required\":{\"type\":\"boolean\"},\"timeout_seconds\":{\"type\":\"integer\"},\"maximum_result_bytes\":{\"type\":\"integer\"}}},\"capability\":{\"type\":\"object\",\"required\":[\"name\",\"description\",\"agent\",\"agent_version\",\"input_schema\",\"output_schema\",\"side_effect\",\"approval_required\"],\"properties\":{\"name\":{\"type\":\"string\"},\"description\":{\"type\":\"string\"},\"agent\":{\"type\":\"string\"},\"agent_version\":{\"type\":\"string\"},\"input_schema\":{\"type\":\"object\"},\"output_schema\":{\"type\":\"object\"},\"side_effect\":{\"type\":\"string\"},\"approval_required\":{\"type\":\"boolean\"},\"timeout_seconds\":{\"type\":\"integer\"},\"maximum_concurrency\":{\"type\":\"integer\"}}}}},\"execution\":{\"type\":\"object\",\"additionalProperties\":false,\"required\":[\"task\",\"context\",\"input_schema\",\"output_schema\",\"mode\",\"maximum_agent_calls\",\"maximum_tool_calls\"],\"properties\":{\"task\":{\"type\":\"string\"},\"context\":{\"type\":\"object\"},\"input_schema\":{\"type\":\"object\"},\"output_schema\":{\"type\":\"object\"},\"mode\":{\"type\":\"string\",\"enum\":[\"advisory\",\"supervised\",\"autonomous\"]},\"maximum_agent_calls\":{\"type\":\"integer\"},\"maximum_tool_calls\":{\"type\":\"integer\"},\"require_approval_for\":{\"type\":\"array\",\"items\":{\"type\":\"string\"}}}}}}",
        "desiredOutputMediaType": "application/json",
        "includeConstraints": true,
        "deadlineSeconds": 600,
        "maximumAgentCalls": 4,
        "maximumToolCalls": 0,
        "allowedCapabilities": "task-understanding,dynamic-planning,structured-generation,result-evaluation,response-composition",
        "metadataInputMode": "json",
        "metadataJson": "={{ JSON.stringify({workflow:'autonomous-agent-workflow-factory', phase:'configuration-planning'}) }}",
        "idempotencyKeyMode": "automatic",
        "waitUntilComplete": true
      },
      "id": "1fab96b6-b9f6-446c-ab28-07cb230944ac",
      "name": "Architect Configuration Blueprint",
      "type": "n8n-nodes-autonomous-agent.autonomousAgentExecution",
      "typeVersion": 1,
      "position": [
        464,
        0
      ],
      "credentials": {
        "autonomousAgentApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "function parseMaybe(value) {\n  if (typeof value !== 'string') {\n    return value;\n  }\n\n  const trimmed = value.trim();\n\n  if (!trimmed) {\n    return value;\n  }\n\n  try {\n    return JSON.parse(trimmed);\n  } catch {\n    return value;\n  }\n}\n\nfunction isPlainObject(value) {\n  return Boolean(\n    value &&\n    typeof value === 'object' &&\n    !Array.isArray(value)\n  );\n}\n\nfunction toFiniteNumber(value, fallback) {\n  const parsed = Number(value);\n  return Number.isFinite(parsed) ? parsed : fallback;\n}\n\nfunction toPositiveInteger(value, fallback, minimum = 0) {\n  const parsed = Math.trunc(toFiniteNumber(value, fallback));\n  return Math.max(parsed, minimum);\n}\n\nfunction toKebab(value, fallback) {\n  const source =\n    typeof value === 'string' && value.trim()\n      ? value\n      : fallback;\n\n  const normalized = String(source)\n    .trim()\n    .toLowerCase()\n    .replace(/[^a-z0-9]+/g, '-')\n    .replace(/^-+|-+$/g, '')\n    .replace(/-{2,}/g, '-');\n\n  if (!normalized) {\n    throw new Error(\n      `Unable to create a valid kebab-case name from ${String(source)}`\n    );\n  }\n\n  return normalized;\n}\n\nfunction uniqueStrings(values) {\n  if (!Array.isArray(values)) {\n    return [];\n  }\n\n  return [\n    ...new Set(\n      values\n        .filter(value => typeof value === 'string')\n        .map(value => value.trim())\n        .filter(Boolean)\n    ),\n  ];\n}\n\nfunction normalizeObjectSchema(schema, requiredByDefault = true) {\n  const parsed = parseMaybe(schema);\n\n  if (!isPlainObject(parsed)) {\n    return {\n      type: 'object',\n      properties: {},\n      required: [],\n      additionalProperties: true,\n    };\n  }\n\n  /*\n   * Preserve complete schemas that use combinators. Converting them into a\n   * property map would destroy valid oneOf/anyOf/allOf contracts.\n   */\n  if (\n    Array.isArray(parsed.oneOf) ||\n    Array.isArray(parsed.anyOf) ||\n    Array.isArray(parsed.allOf)\n  ) {\n    return parsed;\n  }\n\n  const schemaType = parsed.type;\n  const supportsObject =\n    schemaType === 'object' ||\n    (\n      Array.isArray(schemaType) &&\n      schemaType.includes('object')\n    );\n\n  if (supportsObject && isPlainObject(parsed.properties)) {\n    const properties = parsed.properties;\n\n    return {\n      ...parsed,\n      required: Array.isArray(parsed.required)\n        ? uniqueStrings(parsed.required)\n        : requiredByDefault\n          ? Object.keys(properties)\n          : [],\n      additionalProperties:\n        typeof parsed.additionalProperties === 'boolean' ||\n        isPlainObject(parsed.additionalProperties)\n          ? parsed.additionalProperties\n          : false,\n    };\n  }\n\n  /*\n   * An explicitly typed non-object schema must not be converted into an\n   * object schema.\n   */\n  if (\n    typeof schemaType === 'string' &&\n    schemaType !== 'object'\n  ) {\n    return parsed;\n  }\n\n  if (\n    Array.isArray(schemaType) &&\n    !schemaType.includes('object')\n  ) {\n    return parsed;\n  }\n\n  /*\n   * Support legacy bare property maps while excluding JSON Schema metadata.\n   */\n  const metaKeys = new Set([\n    '$schema',\n    '$id',\n    '$ref',\n    '$comment',\n    'title',\n    'description',\n    'type',\n    'properties',\n    'required',\n    'additionalProperties',\n    'definitions',\n    '$defs',\n    'allOf',\n    'anyOf',\n    'oneOf',\n    'not',\n    'if',\n    'then',\n    'else',\n    'dependentSchemas',\n    'dependentRequired',\n    'patternProperties',\n    'propertyNames',\n    'unevaluatedProperties',\n    'examples',\n    'default',\n  ]);\n\n  const propertyEntries = Object.entries(parsed)\n    .filter(([key]) => !metaKeys.has(key));\n\n  if (propertyEntries.length === 0) {\n    return {\n      type: 'object',\n      properties: {},\n      required: [],\n      additionalProperties: true,\n    };\n  }\n\n  const properties = Object.fromEntries(propertyEntries);\n\n  return {\n    type: 'object',\n    properties,\n    required: requiredByDefault\n      ? Object.keys(properties)\n      : [],\n    additionalProperties: false,\n  };\n}\n\nfunction isBlueprint(value) {\n  return Boolean(\n    isPlainObject(value) &&\n    isPlainObject(value.model_policy) &&\n    isPlainObject(value.model_capability) &&\n    isPlainObject(value.execution)\n  );\n}\n\nfunction unwrapKnownEnvelope(value) {\n  let current = parseMaybe(value);\n  const visited = new Set();\n\n  for (let depth = 0; depth < 12; depth += 1) {\n    current = parseMaybe(current);\n\n    if (isBlueprint(current)) {\n      return current;\n    }\n\n    if (!isPlainObject(current)) {\n      return null;\n    }\n\n    /*\n     * Prevent loops if an object is referenced from more than one envelope.\n     */\n    if (visited.has(current)) {\n      return null;\n    }\n\n    visited.add(current);\n\n    const wrappers = [\n      'answer',\n      'output',\n      'result',\n      'structured_output',\n      'response',\n      'data',\n      'blueprint',\n      'value',\n    ];\n\n    let next;\n\n    for (const wrapper of wrappers) {\n      if (current[wrapper] !== undefined) {\n        const candidate = parseMaybe(current[wrapper]);\n\n        if (isBlueprint(candidate)) {\n          return candidate;\n        }\n\n        if (\n          isPlainObject(candidate) ||\n          typeof candidate === 'string'\n        ) {\n          next = candidate;\n          break;\n        }\n      }\n    }\n\n    if (next === undefined) {\n      return null;\n    }\n\n    current = next;\n  }\n\n  return null;\n}\n\nfunction findBlueprint(root) {\n  /*\n   * Check the deterministic runtime locations first.\n   *\n   * Current successful platform contract:\n   *   output.answer -> blueprint\n   *\n   * Older or direct contracts remain supported for backward compatibility.\n   */\n  const candidates = [\n    root?.output?.answer,\n    root?.answer,\n    root?.result?.output?.answer,\n    root?.result?.answer,\n    root?.result?.output,\n    root?.result,\n    root?.output,\n    root?.structured_output,\n    root?.response?.output?.answer,\n    root?.response?.answer,\n    root?.response?.output,\n    root?.response,\n    root?.data?.output?.answer,\n    root?.data?.answer,\n    root?.data?.output,\n    root?.data,\n    root?.task_results?.compose?.output?.answer,\n    root?.task_results?.compose?.output,\n    root?.task_results?.generate?.output,\n    root,\n  ];\n\n  for (const candidate of candidates) {\n    const blueprint = unwrapKnownEnvelope(candidate);\n\n    if (blueprint) {\n      return blueprint;\n    }\n  }\n\n  return null;\n}\n\nfunction assertObject(value, fieldName) {\n  if (!isPlainObject(value)) {\n    throw new Error(`Missing or invalid ${fieldName}`);\n  }\n\n  return value;\n}\n\nfunction normalizeModelPolicy(modelPolicy, modelCapability) {\n  const policy = assertObject(modelPolicy, 'model_policy');\n\n  policy.name = toKebab(\n    policy.name,\n    `${policy.model ?? 'generated-model'}-policy`\n  );\n\n  policy.provider =\n    typeof policy.provider === 'string' && policy.provider.trim()\n      ? policy.provider.trim().toLowerCase()\n      : 'ollama';\n\n  policy.input_modalities =\n    uniqueStrings(policy.input_modalities).length > 0\n      ? uniqueStrings(policy.input_modalities)\n      : ['text'];\n\n  policy.maximum_input_characters = toPositiveInteger(\n    policy.maximum_input_characters,\n    5000,\n    1\n  );\n\n  policy.maximum_output_tokens = toPositiveInteger(\n    policy.maximum_output_tokens,\n    4096,\n    1\n  );\n\n  policy.maximum_cost_units = toPositiveInteger(\n    policy.maximum_cost_units,\n    100,\n    1\n  );\n\n  policy.allowed_purposes = uniqueStrings(\n    policy.allowed_purposes\n  );\n\n  const modelPurpose = modelCapability.model_purpose;\n\n  if (\n    typeof modelPurpose === 'string' &&\n    modelPurpose.trim() &&\n    !policy.allowed_purposes.includes(modelPurpose.trim())\n  ) {\n    policy.allowed_purposes.push(modelPurpose.trim());\n  }\n\n  return policy;\n}\n\nfunction normalizeModelCapability(modelCapability) {\n  const capability = assertObject(\n    modelCapability,\n    'model_capability'\n  );\n\n  capability.name = toKebab(\n    capability.name,\n    'generated-model-capability'\n  );\n\n  capability.agent = 'model-agent';\n  capability.agent_version = '1.0.0';\n\n  capability.model_purpose = toKebab(\n    capability.model_purpose,\n    capability.name\n  );\n\n  capability.input_modalities =\n    uniqueStrings(capability.input_modalities).length > 0\n      ? uniqueStrings(capability.input_modalities)\n      : ['text'];\n\n  capability.timeout_seconds = toPositiveInteger(\n    capability.timeout_seconds,\n    300,\n    300\n  );\n\n  capability.maximum_concurrency = toPositiveInteger(\n    capability.maximum_concurrency,\n    1,\n    1\n  );\n\n  capability.input_schema = normalizeObjectSchema(\n    capability.input_schema,\n    true\n  );\n\n  capability.output_schema = normalizeObjectSchema(\n    capability.output_schema,\n    true\n  );\n\n  return capability;\n}\n\nfunction normalizeTool(tool) {\n  if (tool === null || tool === undefined) {\n    return null;\n  }\n\n  if (!isPlainObject(tool)) {\n    throw new Error(\n      'tool must be an object or null when provided'\n    );\n  }\n\n  const server = assertObject(\n    tool.server,\n    'tool.server'\n  );\n\n  const registration = assertObject(\n    tool.registration,\n    'tool.registration'\n  );\n\n  const capability = assertObject(\n    tool.capability,\n    'tool.capability'\n  );\n\n  server.name = toKebab(\n    server.name,\n    'generated-mcp-server'\n  );\n\n  if (\n    typeof server.base_url !== 'string' ||\n    !server.base_url.trim()\n  ) {\n    throw new Error(\n      'tool.server.base_url must be a non-empty string'\n    );\n  }\n\n  server.base_url = server.base_url.trim();\n\n  server.timeout_seconds = toPositiveInteger(\n    server.timeout_seconds,\n    300,\n    1\n  );\n\n  server.maximum_result_bytes = toPositiveInteger(\n    server.maximum_result_bytes,\n    5 * 1024 * 1024,\n    1\n  );\n\n  registration.name = toKebab(\n    registration.name,\n    'generated-mcp-tool'\n  );\n\n  registration.remote_tool_name =\n    typeof registration.remote_tool_name === 'string' &&\n    registration.remote_tool_name.trim()\n      ? registration.remote_tool_name.trim()\n      : registration.name;\n\n  registration.input_schema = normalizeObjectSchema(\n    registration.input_schema,\n    true\n  );\n\n  registration.output_schema = normalizeObjectSchema(\n    registration.output_schema,\n    false\n  );\n\n  registration.timeout_seconds = toPositiveInteger(\n    registration.timeout_seconds,\n    server.timeout_seconds,\n    1\n  );\n\n  registration.maximum_result_bytes = toPositiveInteger(\n    registration.maximum_result_bytes,\n    server.maximum_result_bytes,\n    1\n  );\n\n  registration.approval_required =\n    registration.approval_required === true;\n\n  registration.side_effect =\n    typeof registration.side_effect === 'string' &&\n    registration.side_effect.trim()\n      ? registration.side_effect.trim()\n      : registration.approval_required\n        ? 'approval-required'\n        : 'external';\n\n  capability.name = toKebab(\n    capability.name,\n    `${registration.name}-capability`\n  );\n\n  capability.agent = 'tool-agent';\n  capability.agent_version = '1.0.0';\n\n  capability.input_schema = normalizeObjectSchema(\n    capability.input_schema,\n    true\n  );\n\n  capability.output_schema = normalizeObjectSchema(\n    capability.output_schema,\n    false\n  );\n\n  capability.timeout_seconds = toPositiveInteger(\n    capability.timeout_seconds,\n    registration.timeout_seconds,\n    1\n  );\n\n  capability.maximum_concurrency = toPositiveInteger(\n    capability.maximum_concurrency,\n    1,\n    1\n  );\n\n  /*\n   * Approval cannot be weakened between the MCP registration and its\n   * capability.\n   */\n  capability.approval_required =\n    capability.approval_required === true ||\n    registration.approval_required === true;\n\n  registration.approval_required =\n    capability.approval_required;\n\n  if (capability.approval_required) {\n    capability.side_effect =\n      typeof capability.side_effect === 'string' &&\n      capability.side_effect.trim()\n        ? capability.side_effect.trim()\n        : 'approval-required';\n  } else {\n    capability.side_effect =\n      typeof capability.side_effect === 'string' &&\n      capability.side_effect.trim()\n        ? capability.side_effect.trim()\n        : 'external';\n  }\n\n  return {\n    server,\n    registration,\n    capability,\n  };\n}\n\nfunction normalizeExecution(execution, tool) {\n  const normalized = assertObject(\n    execution,\n    'execution'\n  );\n\n  normalized.mode = [\n    'advisory',\n    'supervised',\n    'autonomous',\n  ].includes(normalized.mode)\n    ? normalized.mode\n    : 'supervised';\n\n  normalized.task =\n    typeof normalized.task === 'string' &&\n    normalized.task.trim()\n      ? normalized.task.trim()\n      : 'execute-generated-capability';\n\n  normalized.context = isPlainObject(normalized.context)\n    ? normalized.context\n    : {};\n\n  normalized.input_schema = normalizeObjectSchema(\n    normalized.input_schema,\n    true\n  );\n\n  normalized.output_schema = normalizeObjectSchema(\n    normalized.output_schema,\n    true\n  );\n\n  normalized.maximum_agent_calls = toPositiveInteger(\n    normalized.maximum_agent_calls,\n    1,\n    1\n  );\n\n  normalized.maximum_tool_calls = toPositiveInteger(\n    normalized.maximum_tool_calls,\n    tool ? 1 : 0,\n    0\n  );\n\n  const requiredApprovals = uniqueStrings(\n    normalized.require_approval_for\n  );\n\n  if (\n    tool?.registration?.approval_required &&\n    !requiredApprovals.includes(tool.registration.name)\n  ) {\n    requiredApprovals.push(tool.registration.name);\n  }\n\n  normalized.require_approval_for = requiredApprovals;\n\n  return normalized;\n}\n\n\nconst source = $json;\n\nconst blueprint = findBlueprint(source);\n\nif (!blueprint) {\n  throw new Error(\n    [\n      'Planner completed without a recognizable configuration blueprint.',\n      'Expected model_policy, model_capability, and execution either directly,',\n      'under output.answer, under a known transport envelope, or in task_results.',\n    ].join(' ')\n  );\n}\n\nconst normalizedBlueprint = {\n  ...blueprint,\n};\n\nnormalizedBlueprint.model_capability =\n  normalizeModelCapability(\n    normalizedBlueprint.model_capability\n  );\n\nnormalizedBlueprint.model_policy =\n  normalizeModelPolicy(\n    normalizedBlueprint.model_policy,\n    normalizedBlueprint.model_capability\n  );\n\nnormalizedBlueprint.tool =\n  normalizeTool(normalizedBlueprint.tool);\n\nnormalizedBlueprint.execution =\n  normalizeExecution(\n    normalizedBlueprint.execution,\n    normalizedBlueprint.tool\n  );\n\n/*\n * These fields are deployment metadata derived from the normalized resource\n * names. Keep them at the blueprint root so later n8n nodes can construct the\n * final execution request without reparsing nested resources.\n */\nnormalizedBlueprint.allowed_capabilities = [\n  normalizedBlueprint.model_capability.name,\n  ...(\n    normalizedBlueprint.tool\n      ? [normalizedBlueprint.tool.capability.name]\n      : []\n  ),\n];\n\nnormalizedBlueprint.allowed_tools =\n  normalizedBlueprint.tool\n    ? [normalizedBlueprint.tool.registration.name]\n    : [];\n\n/*\n * Omit tool rather than forwarding undefined. Preserve explicit null only\n * when downstream nodes expect the schema's nullable tool property.\n */\nif (!normalizedBlueprint.tool) {\n  normalizedBlueprint.tool = null;\n}\n\nconst plannerExecutionId =\n  source.execution_id ??\n  source.id ??\n  source.execution?.id ??\n  source.output?.execution_id ??\n  null;\n\nreturn [\n  {\n    json: {\n      blueprint: normalizedBlueprint,\n      plannerExecutionId,\n    },\n  },\n];\n"
      },
      "id": "012162be-5d96-4e35-9552-62442ba2d496",
      "name": "Normalize and Validate Blueprint",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        704,
        0
      ]
    },
    {
      "parameters": {
        "jsCode": "const normalized = $json;\nconst parent = $('Sub-workflow Input').item.json;\nconst now = new Date().toISOString();\nconst obs = parent.observability ?? {};\nconst trace = Array.isArray(obs.trace) ? [...obs.trace] : [];\nconst startedAt = obs.stage_started_at ?? obs.started_at ?? now;\nconst event = {timestamp:now,correlation_id:obs.correlation_id,workflow:$workflow.name,execution_id:$execution.id,stage:'configuration-planning',status:'completed',duration_ms:Math.max(0,Date.parse(now)-Date.parse(startedAt)),model:normalized.blueprint?.model_policy?.model ?? null,capability:normalized.blueprint?.model_capability?.name ?? null,custom_tool_required:Boolean(normalized.blueprint?.tool)};\ntrace.push(event); console.log(JSON.stringify({event:'autonomous_agent_workflow_stage',...event}));\nreturn [{json:{...parent,...normalized,observability:{...obs,stage_started_at:now,trace},last_event:event}}];"
      },
      "id": "6cdedd58-1043-4479-aa26-6dd2bc1d2fef",
      "name": "Complete Planning Trace",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        944,
        0
      ]
    }
  ],
  "connections": {
    "Sub-workflow Input": {
      "main": [
        [
          {
            "node": "Start Planning Trace",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Start Planning Trace": {
      "main": [
        [
          {
            "node": "Architect Configuration Blueprint",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Architect Configuration Blueprint": {
      "main": [
        [
          {
            "node": "Normalize and Validate Blueprint",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Normalize and Validate Blueprint": {
      "main": [
        [
          {
            "node": "Complete Planning Trace",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": true,
  "settings": {
    "executionOrder": "v1",
    "binaryMode": "separate",
    "availableInMCP": false
  },
  "versionId": "78100362-96fe-4851-b9c5-4ed1ba35ae6a",
  "nodeGroups": [],
  "id": "OADovygyoqfcjlpd",
  "tags": [
    {
      "name": "factory",
      "id": "RafL6i7R2YphAkeq",
      "updatedAt": "2026-07-26T11:35:57.670Z",
      "createdAt": "2026-07-26T11:35:57.670Z"
    }
  ]
}