diff --git a/.github/workflows/benchmark-nightly.yml b/.github/workflows/benchmark-nightly.yml
index 2de86abaf740e..d557a0c9ee6e3 100644
--- a/.github/workflows/benchmark-nightly.yml
+++ b/.github/workflows/benchmark-nightly.yml
@@ -24,6 +24,9 @@ env:
   ARM_SUBSCRIPTION_ID: ${{ secrets.BENCHMARK_ARM_SUBSCRIPTION_ID }}
   ARM_TENANT_ID: ${{ secrets.BENCHMARK_ARM_TENANT_ID }}
   K6_API_TOKEN: ${{ secrets.K6_API_TOKEN }}
+  N8N_TAG: ${{ inputs.n8n_tag || 'nightly' }}
+  N8N_BENCHMARK_TAG: ${{ inputs.benchmark_tag || 'latest' }}
+  DEBUG: ${{ inputs.debug == 'true' && '--debug' || '' }}
 
 permissions:
   id-token: write
@@ -62,12 +65,23 @@ jobs:
         run: pnpm destroy-cloud-env
         working-directory: packages/@n8n/benchmark
 
-      - name: Run the benchmark with debug logging
-        if: github.event.inputs.debug == 'true'
-        run: pnpm benchmark-in-cloud --n8nTag ${{ inputs.n8n_tag || 'nightly' }} --benchmarkTag ${{ inputs.benchmark_tag || 'latest' }} --debug
+      - name: Provision the environment
+        run: pnpm provision-cloud-env ${{ env.DEBUG }}
         working-directory: packages/@n8n/benchmark
 
       - name: Run the benchmark
-        if: github.event.inputs.debug != 'true'
-        run: pnpm benchmark-in-cloud --n8nTag ${{ inputs.n8n_tag || 'nightly' }} --benchmarkTag ${{ inputs.benchmark_tag || 'latest' }}
+        run: pnpm benchmark-in-cloud --n8nTag ${{ env.N8N_TAG }} --benchmarkTag ${{ env.N8N_BENCHMARK_TAG }} ${{ env.DEBUG }}
+        working-directory: packages/@n8n/benchmark
+
+        # We need to login again because the access token expires
+      - name: Azure login
+        uses: azure/login@v2.1.1
+        with:
+          client-id: ${{ env.ARM_CLIENT_ID }}
+          tenant-id: ${{ env.ARM_TENANT_ID }}
+          subscription-id: ${{ env.ARM_SUBSCRIPTION_ID }}
+
+      - name: Destroy the environment
+        if: always()
+        run: pnpm destroy-cloud-env ${{ env.DEBUG }}
         working-directory: packages/@n8n/benchmark
diff --git a/packages/@n8n/benchmark/package.json b/packages/@n8n/benchmark/package.json
index 2d9affef5dbc5..d7a8fb8a5f231 100644
--- a/packages/@n8n/benchmark/package.json
+++ b/packages/@n8n/benchmark/package.json
@@ -13,6 +13,7 @@
     "benchmark": "zx scripts/run.mjs",
     "benchmark-in-cloud": "pnpm benchmark --env cloud",
     "benchmark-locally": "pnpm benchmark --env local",
+    "provision-cloud-env": "zx scripts/provisionCloudEnv.mjs",
     "destroy-cloud-env": "zx scripts/destroyCloudEnv.mjs",
     "watch": "concurrently \"tsc -w -p tsconfig.build.json\" \"tsc-alias -w -p tsconfig.build.json\""
   },
diff --git a/packages/@n8n/benchmark/scripts/clients/terraformClient.mjs b/packages/@n8n/benchmark/scripts/clients/terraformClient.mjs
index b156998f92d7a..522e35b6e98e4 100644
--- a/packages/@n8n/benchmark/scripts/clients/terraformClient.mjs
+++ b/packages/@n8n/benchmark/scripts/clients/terraformClient.mjs
@@ -17,6 +17,16 @@ export class TerraformClient {
 		});
 	}
 
+	/**
+	 * Provisions the environment
+	 */
+	async provisionEnvironment() {
+		console.log('Provisioning cloud environment...');
+
+		await this.$$`terraform init`;
+		await this.$$`terraform apply -input=false -auto-approve`;
+	}
+
 	/**
 	 * @typedef {Object} BenchmarkEnv
 	 * @property {string} vmName
@@ -26,12 +36,7 @@ export class TerraformClient {
 	 *
 	 * @returns {Promise<BenchmarkEnv>}
 	 */
-	async provisionEnvironment() {
-		console.log('Provisioning cloud environment...');
-
-		await this.$$`terraform init`;
-		await this.$$`terraform apply -input=false -auto-approve`;
-
+	async getTerraformOutputs() {
 		const privateKeyName = await this.extractPrivateKey();
 
 		return {
@@ -42,12 +47,11 @@ export class TerraformClient {
 		};
 	}
 
-	async destroyEnvironment() {
-		if (!fs.existsSync(paths.terraformStateFile)) {
-			console.log('No cloud environment to destroy. Skipping...');
-			return;
-		}
+	hasTerraformState() {
+		return fs.existsSync(paths.terraformStateFile);
+	}
 
+	async destroyEnvironment() {
 		console.log('Destroying cloud environment...');
 
 		await this.$$`terraform destroy -input=false -auto-approve`;
diff --git a/packages/@n8n/benchmark/scripts/destroyCloudEnv.mjs b/packages/@n8n/benchmark/scripts/destroyCloudEnv.mjs
index 1ffc852aeaab8..0e203efca353a 100644
--- a/packages/@n8n/benchmark/scripts/destroyCloudEnv.mjs
+++ b/packages/@n8n/benchmark/scripts/destroyCloudEnv.mjs
@@ -1,52 +1,43 @@
 #!/usr/bin/env zx
 /**
- * Script that deletes all resources created by the benchmark environment
- * and that are older than 2 hours.
+ * Script that deletes all resources created by the benchmark environment.
  *
- * Even tho the environment is provisioned using terraform, the terraform
- * state is not persisted. Hence we can't use terraform to delete the resources.
- * We could store the state to a storage account, but then we wouldn't be able
- * to spin up new envs on-demand. Hence this design.
- *
- * Usage:
- * 	 zx scripts/deleteCloudEnv.mjs
+ * This scripts tries to delete resources created by Terraform. If Terraform
+ * state file is not found, it will try to delete resources using Azure CLI.
+ * The terraform state is not persisted, so we want to support both cases.
  */
 // @ts-check
-import { $ } from 'zx';
+import { $, minimist } from 'zx';
+import { TerraformClient } from './clients/terraformClient.mjs';
 
-const EXPIRE_TIME_IN_H = 2;
-const EXPIRE_TIME_IN_MS = EXPIRE_TIME_IN_H * 60 * 60 * 1000;
 const RESOURCE_GROUP_NAME = 'n8n-benchmarking';
 
+const args = minimist(process.argv.slice(3), {
+	boolean: ['debug'],
+});
+
+const isVerbose = !!args.debug;
+
 async function main() {
+	const terraformClient = new TerraformClient({ isVerbose });
+
+	if (terraformClient.hasTerraformState()) {
+		await terraformClient.destroyEnvironment();
+	} else {
+		await destroyUsingAz();
+	}
+}
+
+async function destroyUsingAz() {
 	const resourcesResult =
 		await $`az resource list --resource-group ${RESOURCE_GROUP_NAME} --query "[?tags.Id == 'N8nBenchmark'].{id:id, createdAt:tags.CreatedAt}" -o json`;
 
 	const resources = JSON.parse(resourcesResult.stdout);
 
-	const now = Date.now();
-
-	const resourcesToDelete = resources
-		.filter((resource) => {
-			if (resource.createdAt === undefined) {
-				return true;
-			}
-
-			const createdAt = new Date(resource.createdAt);
-			const resourceExpiredAt = createdAt.getTime() + EXPIRE_TIME_IN_MS;
-
-			return now > resourceExpiredAt;
-		})
-		.map((resource) => resource.id);
+	const resourcesToDelete = resources.map((resource) => resource.id);
 
 	if (resourcesToDelete.length === 0) {
-		if (resources.length === 0) {
-			console.log('No resources found in the resource group.');
-		} else {
-			console.log(
-				`Found ${resources.length} resources in the resource group, but none are older than ${EXPIRE_TIME_IN_H} hours.`,
-			);
-		}
+		console.log('No resources found in the resource group.');
 
 		return;
 	}
@@ -87,4 +78,9 @@ async function deleteById(id) {
 	}
 }
 
-main();
+main().catch((error) => {
+	console.error('An error occurred destroying cloud env:');
+	console.error(error);
+
+	process.exit(1);
+});
diff --git a/packages/@n8n/benchmark/scripts/provisionCloudEnv.mjs b/packages/@n8n/benchmark/scripts/provisionCloudEnv.mjs
new file mode 100644
index 0000000000000..5f10e7c60c72f
--- /dev/null
+++ b/packages/@n8n/benchmark/scripts/provisionCloudEnv.mjs
@@ -0,0 +1,36 @@
+#!/usr/bin/env zx
+/**
+ * Provisions the cloud benchmark environment
+ *
+ * NOTE: Must be run in the root of the package.
+ */
+// @ts-check
+import { which, minimist } from 'zx';
+import { TerraformClient } from './clients/terraformClient.mjs';
+
+const args = minimist(process.argv.slice(3), {
+	boolean: ['debug'],
+});
+
+const isVerbose = !!args.debug;
+
+export async function provision() {
+	await ensureDependencies();
+
+	const terraformClient = new TerraformClient({
+		isVerbose,
+	});
+
+	await terraformClient.provisionEnvironment();
+}
+
+async function ensureDependencies() {
+	await which('terraform');
+}
+
+provision().catch((error) => {
+	console.error('An error occurred while provisioning cloud env:');
+	console.error(error);
+
+	process.exit(1);
+});
diff --git a/packages/@n8n/benchmark/scripts/run.mjs b/packages/@n8n/benchmark/scripts/run.mjs
index ece2e942d6f14..a276eee5fcc50 100755
--- a/packages/@n8n/benchmark/scripts/run.mjs
+++ b/packages/@n8n/benchmark/scripts/run.mjs
@@ -1,12 +1,9 @@
 #!/usr/bin/env zx
 /**
  * Script to run benchmarks either on the cloud benchmark environment or locally.
+ * The cloud environment needs to be provisioned using Terraform before running the benchmarks.
  *
  * NOTE: Must be run in the root of the package.
- *
- * Usage:
- * 	 zx scripts/run.mjs
- *
  */
 // @ts-check
 import fs from 'fs';
diff --git a/packages/@n8n/benchmark/scripts/runInCloud.mjs b/packages/@n8n/benchmark/scripts/runInCloud.mjs
index 8730807a6ed6c..1c1b191ca9a0d 100755
--- a/packages/@n8n/benchmark/scripts/runInCloud.mjs
+++ b/packages/@n8n/benchmark/scripts/runInCloud.mjs
@@ -39,16 +39,9 @@ export async function runInCloud(config) {
 		isVerbose: config.isVerbose,
 	});
 
-	try {
-		const benchmarkEnv = await terraformClient.provisionEnvironment();
+	const benchmarkEnv = await terraformClient.getTerraformOutputs();
 
-		await runBenchmarksOnVm(config, benchmarkEnv);
-	} catch (error) {
-		console.error('An error occurred while running the benchmarks:');
-		console.error(error);
-	} finally {
-		await terraformClient.destroyEnvironment();
-	}
+	await runBenchmarksOnVm(config, benchmarkEnv);
 }
 
 async function ensureDependencies() {
@@ -117,7 +110,15 @@ async function runBenchmarkForN8nSetup({ config, sshClient, scriptsDir, n8nSetup
 }
 
 async function ensureVmIsReachable(sshClient) {
-	await sshClient.ssh('echo "VM is reachable"');
+	try {
+		await sshClient.ssh('echo "VM is reachable"');
+	} catch (error) {
+		console.error(`VM is not reachable: ${error.message}`);
+		console.error(
+			`Did you provision the cloud environment first with 'pnpm provision-cloud-env'? You can also run the benchmarks locally with 'pnpm run benchmark-locally'.`,
+		);
+		process.exit(1);
+	}
 }
 
 /**
diff --git a/packages/@n8n/nodes-langchain/utils/helpers.ts b/packages/@n8n/nodes-langchain/utils/helpers.ts
index 05b4a5cadc6f7..bdac2048b2d9c 100644
--- a/packages/@n8n/nodes-langchain/utils/helpers.ts
+++ b/packages/@n8n/nodes-langchain/utils/helpers.ts
@@ -1,10 +1,10 @@
+import { NodeConnectionType, NodeOperationError, jsonStringify } from 'n8n-workflow';
 import type {
 	EventNamesAiNodesType,
 	IDataObject,
 	IExecuteFunctions,
 	IWebhookFunctions,
 } from 'n8n-workflow';
-import { NodeConnectionType, NodeOperationError, jsonStringify } from 'n8n-workflow';
 import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
 import type { BaseOutputParser } from '@langchain/core/output_parsers';
 import type { BaseMessage } from '@langchain/core/messages';
diff --git a/packages/cli/src/load-nodes-and-credentials.ts b/packages/cli/src/load-nodes-and-credentials.ts
index 5b2cc284e93dc..eb6c55fa5dd7e 100644
--- a/packages/cli/src/load-nodes-and-credentials.ts
+++ b/packages/cli/src/load-nodes-and-credentials.ts
@@ -13,11 +13,12 @@ import {
 } from 'n8n-core';
 import type {
 	KnownNodesAndCredentials,
+	INodeTypeBaseDescription,
 	INodeTypeDescription,
 	INodeTypeData,
 	ICredentialTypeData,
 } from 'n8n-workflow';
-import { ApplicationError, ErrorReporterProxy as ErrorReporter } from 'n8n-workflow';
+import { NodeHelpers, ApplicationError, ErrorReporterProxy as ErrorReporter } from 'n8n-workflow';
 
 import {
 	CUSTOM_API_CALL_KEY,
@@ -38,8 +39,11 @@ interface LoadedNodesAndCredentials {
 export class LoadNodesAndCredentials {
 	private known: KnownNodesAndCredentials = { nodes: {}, credentials: {} };
 
+	// This contains the actually loaded objects, and their source paths
 	loaded: LoadedNodesAndCredentials = { nodes: {}, credentials: {} };
 
+	// For nodes, this only contains the descriptions, loaded from either the
+	// actual file, or the lazy loaded json
 	types: Types = { nodes: [], credentials: [] };
 
 	loaders: Record<string, DirectoryLoader> = {};
@@ -260,6 +264,34 @@ export class LoadNodesAndCredentials {
 		return loader;
 	}
 
+	/**
+	 * This creates all AI Agent tools by duplicating the node descriptions for
+	 * all nodes that are marked as `usableAsTool`. It basically modifies the
+	 * description. The actual wrapping happens in the langchain code for getting
+	 * the connected tools.
+	 */
+	createAiTools() {
+		const usableNodes: Array<INodeTypeBaseDescription | INodeTypeDescription> =
+			this.types.nodes.filter((nodetype) => nodetype.usableAsTool === true);
+
+		for (const usableNode of usableNodes) {
+			const description: INodeTypeBaseDescription | INodeTypeDescription =
+				structuredClone(usableNode);
+			const wrapped = NodeHelpers.convertNodeToAiTool({ description }).description;
+
+			this.types.nodes.push(wrapped);
+			this.known.nodes[wrapped.name] = structuredClone(this.known.nodes[usableNode.name]);
+
+			const credentialNames = Object.entries(this.known.credentials)
+				.filter(([_, credential]) => credential?.supportedNodes?.includes(usableNode.name))
+				.map(([credentialName]) => credentialName);
+
+			credentialNames.forEach((name) =>
+				this.known.credentials[name]?.supportedNodes?.push(wrapped.name),
+			);
+		}
+	}
+
 	async postProcessLoaders() {
 		this.known = { nodes: {}, credentials: {} };
 		this.loaded = { nodes: {}, credentials: {} };
@@ -307,6 +339,8 @@ export class LoadNodesAndCredentials {
 			}
 		}
 
+		this.createAiTools();
+
 		this.injectCustomApiCallOptions();
 
 		for (const postProcessor of this.postProcessors) {
diff --git a/packages/cli/src/node-types.ts b/packages/cli/src/node-types.ts
index dc8ea2860cda9..550b836a16c66 100644
--- a/packages/cli/src/node-types.ts
+++ b/packages/cli/src/node-types.ts
@@ -43,7 +43,15 @@ export class NodeTypes implements INodeTypes {
 	}
 
 	getByNameAndVersion(nodeType: string, version?: number): INodeType {
-		return NodeHelpers.getVersionedNodeType(this.getNode(nodeType).type, version);
+		const versionedNodeType = NodeHelpers.getVersionedNodeType(
+			this.getNode(nodeType).type,
+			version,
+		);
+		if (versionedNodeType.description.usableAsTool) {
+			return NodeHelpers.convertNodeToAiTool(versionedNodeType);
+		}
+
+		return versionedNodeType;
 	}
 
 	/* Some nodeTypes need to get special parameters applied like the polling nodes the polling times */
@@ -66,8 +74,9 @@ export class NodeTypes implements INodeTypes {
 
 		if (type in knownNodes) {
 			const { className, sourcePath } = knownNodes[type];
-			const loaded: INodeType = loadClassInIsolation(sourcePath, className);
-			NodeHelpers.applySpecialNodeParameters(loaded);
+			const loaded: INodeType | IVersionedNodeType = loadClassInIsolation(sourcePath, className);
+			if (NodeHelpers.isINodeType(loaded)) NodeHelpers.applySpecialNodeParameters(loaded);
+
 			loadedNodes[type] = { sourcePath, type: loaded };
 			return loadedNodes[type];
 		}
diff --git a/packages/core/package.json b/packages/core/package.json
index 04412c4c8ce14..f0de12c0f49bc 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -44,6 +44,7 @@
     "file-type": "16.5.4",
     "form-data": "catalog:",
     "lodash": "catalog:",
+    "@langchain/core": "0.2.18",
     "luxon": "catalog:",
     "mime-types": "2.1.35",
     "n8n-workflow": "workspace:*",
@@ -54,6 +55,7 @@
     "ssh2": "1.15.0",
     "typedi": "catalog:",
     "uuid": "catalog:",
-    "xml2js": "catalog:"
+    "xml2js": "catalog:",
+    "zod": "catalog:"
   }
 }
diff --git a/packages/core/src/CreateNodeAsTool.ts b/packages/core/src/CreateNodeAsTool.ts
new file mode 100644
index 0000000000000..21e1b6352ad1a
--- /dev/null
+++ b/packages/core/src/CreateNodeAsTool.ts
@@ -0,0 +1,296 @@
+/**
+ * @module NodeAsTool
+ * @description This module converts n8n nodes into LangChain tools by analyzing node parameters,
+ * identifying placeholders, and generating a Zod schema. It then creates a DynamicStructuredTool
+ * that can be used in LangChain workflows.
+ *
+ * General approach:
+ * 1. Recursively traverse node parameters to find placeholders, including in nested structures
+ * 2. Generate a Zod schema based on these placeholders, preserving the nested structure
+ * 3. Create a DynamicStructuredTool with the schema and a function that executes the n8n node
+ *
+ * Example:
+ * - Node parameters:
+ *   {
+ *     "inputText": "{{ '__PLACEHOLDER: Enter main text to process' }}",
+ *     "options": {
+ *       "language": "{{ '__PLACEHOLDER: Specify language' }}",
+ *       "advanced": {
+ *         "maxLength": "{{ '__PLACEHOLDER: Enter maximum length' }}"
+ *       }
+ *     }
+ *   }
+ *
+ * - Generated Zod schema:
+ *   z.object({
+ *     "inputText": z.string().describe("Enter main text to process"),
+ *     "options__language": z.string().describe("Specify language"),
+ *     "options__advanced__maxLength": z.string().describe("Enter maximum length")
+ *   }).required()
+ *
+ * - Resulting tool can be called with:
+ *   {
+ *     "inputText": "Hello, world!",
+ *     "options__language": "en",
+ *     "options__advanced__maxLength": "100"
+ *   }
+ *
+ * Note: Nested properties are flattened with double underscores in the schema,
+ * but the tool reconstructs the original nested structure when executing the node.
+ */
+
+import { DynamicStructuredTool } from '@langchain/core/tools';
+import {
+	NodeConnectionType,
+	type IExecuteFunctions,
+	type INodeParameters,
+	type INodeType,
+} from 'n8n-workflow';
+import { z } from 'zod';
+
+/** Represents a nested object structure */
+type NestedObject = { [key: string]: unknown };
+
+/**
+ * Encodes a dot-notated key to a format safe for use as an object key.
+ * @param {string} key - The dot-notated key to encode.
+ * @returns {string} The encoded key.
+ */
+function encodeDotNotation(key: string): string {
+	// Replace dots with double underscores, then handle special case for '__value' for complicated params
+	return key.replace(/\./g, '__').replace('__value', '');
+}
+
+/**
+ * Decodes an encoded key back to its original dot-notated form.
+ * @param {string} key - The encoded key to decode.
+ * @returns {string} The decoded, dot-notated key.
+ */
+function decodeDotNotation(key: string): string {
+	// Simply replace double underscores with dots
+	return key.replace(/__/g, '.');
+}
+
+/**
+ * Recursively traverses an object to find placeholder values.
+ * @param {NestedObject} obj - The object to traverse.
+ * @param {string[]} path - The current path in the object.
+ * @param {Map<string, string>} results - Map to store found placeholders.
+ * @returns {Map<string, string>} Updated map of placeholders.
+ */
+function traverseObject(
+	obj: NestedObject,
+	path: string[] = [],
+	results: Map<string, string> = new Map(),
+): Map<string, string> {
+	for (const [key, value] of Object.entries(obj)) {
+		const currentPath = [...path, key];
+		const fullPath = currentPath.join('.');
+
+		if (typeof value === 'string' && value.startsWith("{{ '__PLACEHOLDER")) {
+			// Store placeholder values with their full path
+			results.set(encodeDotNotation(fullPath), value);
+		} else if (Array.isArray(value)) {
+			// Recursively traverse arrays
+			// eslint-disable-next-line @typescript-eslint/no-use-before-define
+			traverseArray(value, currentPath, results);
+		} else if (typeof value === 'object' && value !== null) {
+			// Recursively traverse nested objects, but only if they're not empty
+			if (Object.keys(value).length > 0) {
+				traverseObject(value as NestedObject, currentPath, results);
+			}
+		}
+	}
+
+	return results;
+}
+
+/**
+ * Recursively traverses an array to find placeholder values.
+ * @param {unknown[]} arr - The array to traverse.
+ * @param {string[]} path - The current path in the array.
+ * @param {Map<string, string>} results - Map to store found placeholders.
+ */
+function traverseArray(arr: unknown[], path: string[], results: Map<string, string>): void {
+	arr.forEach((item, index) => {
+		const currentPath = [...path, index.toString()];
+		const fullPath = currentPath.join('.');
+
+		if (typeof item === 'string' && item.startsWith("{{ '__PLACEHOLDER")) {
+			// Store placeholder values with their full path
+			results.set(encodeDotNotation(fullPath), item);
+		} else if (Array.isArray(item)) {
+			// Recursively traverse nested arrays
+			traverseArray(item, currentPath, results);
+		} else if (typeof item === 'object' && item !== null) {
+			// Recursively traverse nested objects
+			traverseObject(item as NestedObject, currentPath, results);
+		}
+	});
+}
+
+/**
+ * Builds a nested object structure from matching keys and their values.
+ * @param {string} baseKey - The base key to start building from.
+ * @param {string[]} matchingKeys - Array of matching keys.
+ * @param {Record<string, string>} values - Object containing values for the keys.
+ * @returns {Record<string, unknown>} The built nested object structure.
+ */
+function buildStructureFromMatches(
+	baseKey: string,
+	matchingKeys: string[],
+	values: Record<string, string>,
+): Record<string, unknown> {
+	const result = {};
+
+	for (const matchingKey of matchingKeys) {
+		const decodedKey = decodeDotNotation(matchingKey);
+		// Extract the part of the key after the base key
+		const remainingPath = decodedKey
+			.slice(baseKey.length)
+			.split('.')
+			.filter((k) => k !== '');
+		let current: Record<string, unknown> = result;
+
+		// Build the nested structure
+		for (let i = 0; i < remainingPath.length - 1; i++) {
+			if (!(remainingPath[i] in current)) {
+				current[remainingPath[i]] = {};
+			}
+			current = current[remainingPath[i]] as Record<string, unknown>;
+		}
+
+		// Set the value at the deepest level
+		const lastKey = remainingPath[remainingPath.length - 1];
+		current[lastKey ?? matchingKey] = values[matchingKey];
+	}
+
+	// If no nested structure was created, return the direct value
+	return Object.keys(result).length === 0 ? values[encodeDotNotation(baseKey)] : result;
+}
+
+/**
+ * Extracts the description from a placeholder string.
+ * @param {string} value - The placeholder string.
+ * @returns {string} The extracted description or a default message.
+ */
+function extractPlaceholderDescription(value: string): string {
+	const match = value.match(/{{ '__PLACEHOLDER:\s*(.+?)\s*' }}/);
+	return match ? match[1] : 'No description provided';
+}
+
+/**
+ * Creates a DynamicStructuredTool from an n8n node.
+ * @param {INodeType} node - The n8n node to convert.
+ * @param {IExecuteFunctions} ctx - The execution context.
+ * @param {INodeParameters} nodeParameters - The node parameters.
+ * @returns {DynamicStructuredTool} The created tool.
+ */
+export function createNodeAsTool(
+	node: INodeType,
+	ctx: IExecuteFunctions,
+	nodeParameters: INodeParameters,
+): DynamicStructuredTool {
+	// Find all placeholder values in the node parameters
+	const placeholderValues = traverseObject(nodeParameters);
+
+	// Generate Zod schema from placeholder values
+	const schemaObj: { [key: string]: z.ZodString } = {};
+	for (const [key, value] of placeholderValues.entries()) {
+		const description = extractPlaceholderDescription(value);
+		schemaObj[key] = z.string().describe(description);
+	}
+	const schema = z.object(schemaObj).required();
+
+	// Get the tool description from node parameters or use the default
+	const toolDescription = ctx.getNodeParameter(
+		'toolDescription',
+		0,
+		node.description.description,
+	) as string;
+	type GetNodeParameterMethod = IExecuteFunctions['getNodeParameter'];
+
+	const tool = new DynamicStructuredTool({
+		name: node.description.name,
+		description: toolDescription ? toolDescription : node.description.description,
+		schema,
+		func: async (functionArgs: z.infer<typeof schema>) => {
+			// Create a proxy for ctx to soft-override parameters with values from the LLM
+			const ctxProxy = new Proxy(ctx, {
+				get(target: IExecuteFunctions, prop: string | symbol, receiver: unknown) {
+					if (prop === 'getNodeParameter') {
+						// Override getNodeParameter method
+						// eslint-disable-next-line @typescript-eslint/unbound-method
+						return new Proxy(target.getNodeParameter, {
+							apply(
+								targetMethod: GetNodeParameterMethod,
+								thisArg: unknown,
+								argumentsList: Parameters<GetNodeParameterMethod>,
+							): ReturnType<GetNodeParameterMethod> {
+								const [key] = argumentsList;
+								if (typeof key !== 'string') {
+									// If key is not a string, use the original method
+									return Reflect.apply(targetMethod, thisArg, argumentsList);
+								}
+
+								const encodedKey = encodeDotNotation(key);
+								// Check if the full key or any more specific key is a placeholder
+								const matchingKeys = Array.from(placeholderValues.keys()).filter((k) =>
+									k.startsWith(encodedKey),
+								);
+
+								if (matchingKeys.length > 0) {
+									// If there are matching keys, build the structure using args
+									const res = buildStructureFromMatches(encodedKey, matchingKeys, functionArgs);
+									// Return either the specific value or the entire built structure
+									return res?.[decodeDotNotation(key)] ?? res;
+								}
+
+								// If no placeholder is found, use the original function
+								return Reflect.apply(targetMethod, thisArg, argumentsList);
+							},
+						});
+					}
+					// eslint-disable-next-line @typescript-eslint/no-unsafe-return
+					return Reflect.get(target, prop, receiver);
+				},
+			});
+
+			// Add input data to the context
+			ctxProxy.addInputData(NodeConnectionType.AiTool, [[{ json: functionArgs }]]);
+
+			// Execute the node with the proxied context
+			const result = await node.execute?.bind(ctxProxy)();
+
+			// Process and map the results
+			const mappedResults = result?.[0]?.flatMap((item) => item.json);
+
+			// Add output data to the context
+			ctxProxy.addOutputData(NodeConnectionType.AiTool, 0, [
+				[{ json: { response: mappedResults } }],
+			]);
+
+			// Return the stringified results
+			return JSON.stringify(mappedResults);
+		},
+	});
+
+	return tool;
+}
+
+/**
+ * Asynchronously creates a DynamicStructuredTool from an n8n node.
+ * @param {IExecuteFunctions} ctx - The execution context.
+ * @param {INodeType} node - The n8n node to convert.
+ * @param {INodeParameters} nodeParameters - The node parameters.
+ * @returns {Promise<{response: DynamicStructuredTool}>} A promise that resolves to an object containing the created tool.
+ */
+export function getNodeAsTool(
+	ctx: IExecuteFunctions,
+	node: INodeType,
+	nodeParameters: INodeParameters,
+) {
+	return {
+		response: createNodeAsTool(node, ctx, nodeParameters),
+	};
+}
diff --git a/packages/core/src/DirectoryLoader.ts b/packages/core/src/DirectoryLoader.ts
index 77c107ef732a6..717edd5359de8 100644
--- a/packages/core/src/DirectoryLoader.ts
+++ b/packages/core/src/DirectoryLoader.ts
@@ -40,14 +40,20 @@ export type Types = {
 export abstract class DirectoryLoader {
 	isLazyLoaded = false;
 
+	// Another way of keeping track of the names and versions of a node. This
+	// seems to only be used by the installedPackages repository
 	loadedNodes: INodeTypeNameVersion[] = [];
 
+	// Stores the loaded descriptions and sourcepaths
 	nodeTypes: INodeTypeData = {};
 
 	credentialTypes: ICredentialTypeData = {};
 
+	// Stores the location and classnames of the nodes and credentials that are
+	// loaded; used to actually load the files in lazy-loading scenario.
 	known: KnownNodesAndCredentials = { nodes: {}, credentials: {} };
 
+	// Stores the different versions with their individual descriptions
 	types: Types = { nodes: [], credentials: [] };
 
 	protected nodesByCredential: Record<string, string[]> = {};
diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts
index c4c00bd3c8013..3f7be2a7050df 100644
--- a/packages/core/src/NodeExecuteFunctions.ts
+++ b/packages/core/src/NodeExecuteFunctions.ts
@@ -159,6 +159,7 @@ import { InstanceSettings } from './InstanceSettings';
 import { ScheduledTaskManager } from './ScheduledTaskManager';
 import { SSHClientsManager } from './SSHClientsManager';
 import { binaryToBuffer } from './BinaryData/utils';
+import { getNodeAsTool } from './CreateNodeAsTool';
 
 axios.defaults.timeout = 300000;
 // Prevent axios from adding x-form-www-urlencoded headers by default
@@ -2780,12 +2781,6 @@ async function getInputConnectionData(
 				connectedNode.typeVersion,
 			);
 
-			if (!nodeType.supplyData) {
-				throw new ApplicationError('Node does not have a `supplyData` method defined', {
-					extra: { nodeName: connectedNode.name },
-				});
-			}
-
 			const context = Object.assign({}, this);
 
 			context.getNodeParameter = (
@@ -2853,6 +2848,18 @@ async function getInputConnectionData(
 				}
 			};
 
+			if (!nodeType.supplyData) {
+				if (nodeType.description.outputs.includes(NodeConnectionType.AiTool)) {
+					nodeType.supplyData = async function (this: IExecuteFunctions) {
+						return getNodeAsTool(this, nodeType, this.getNode().parameters);
+					};
+				} else {
+					throw new ApplicationError('Node does not have a `supplyData` method defined', {
+						extra: { nodeName: connectedNode.name },
+					});
+				}
+			}
+
 			try {
 				const response = await nodeType.supplyData.call(context, itemIndex);
 				if (response.closeFunction) {
diff --git a/packages/core/test/CreateNodeAsTool.test.ts b/packages/core/test/CreateNodeAsTool.test.ts
new file mode 100644
index 0000000000000..c4509e08be239
--- /dev/null
+++ b/packages/core/test/CreateNodeAsTool.test.ts
@@ -0,0 +1,92 @@
+import { createNodeAsTool } from '@/CreateNodeAsTool';
+import type { IExecuteFunctions, INodeParameters, INodeType } from 'n8n-workflow';
+import { NodeConnectionType } from 'n8n-workflow';
+import { z } from 'zod';
+
+jest.mock('@langchain/core/tools', () => ({
+	DynamicStructuredTool: jest.fn().mockImplementation((config) => ({
+		name: config.name,
+		description: config.description,
+		schema: config.schema,
+		func: config.func,
+	})),
+}));
+
+describe('createNodeAsTool', () => {
+	let mockCtx: IExecuteFunctions;
+	let mockNode: INodeType;
+	let mockNodeParameters: INodeParameters;
+
+	beforeEach(() => {
+		mockCtx = {
+			getNodeParameter: jest.fn(),
+			addInputData: jest.fn(),
+			addOutputData: jest.fn(),
+		} as unknown as IExecuteFunctions;
+
+		mockNode = {
+			description: {
+				name: 'TestNode',
+				description: 'Test node description',
+			},
+			execute: jest.fn().mockResolvedValue([[{ json: { result: 'test' } }]]),
+		} as unknown as INodeType;
+
+		mockNodeParameters = {
+			param1: "{{ '__PLACEHOLDER: Test parameter' }}",
+			param2: 'static value',
+			nestedParam: {
+				subParam: "{{ '__PLACEHOLDER: Nested parameter' }}",
+			},
+		};
+		jest.clearAllMocks();
+	});
+
+	it('should create a DynamicStructuredTool with correct properties', () => {
+		const tool = createNodeAsTool(mockNode, mockCtx, mockNodeParameters);
+
+		expect(tool).toBeDefined();
+		expect(tool.name).toBe('TestNode');
+		expect(tool.description).toBe('Test node description');
+		expect(tool.schema).toBeDefined();
+	});
+
+	it('should use toolDescription if provided', () => {
+		const customDescription = 'Custom tool description';
+		(mockCtx.getNodeParameter as jest.Mock).mockReturnValue(customDescription);
+
+		const tool = createNodeAsTool(mockNode, mockCtx, mockNodeParameters);
+
+		expect(tool.description).toBe(customDescription);
+	});
+
+	it('should create a schema based on placeholder values in nodeParameters', () => {
+		const tool = createNodeAsTool(mockNode, mockCtx, mockNodeParameters);
+
+		expect(tool.schema).toBeDefined();
+		expect(tool.schema.shape).toHaveProperty('param1');
+		expect(tool.schema.shape).toHaveProperty('nestedParam__subParam');
+		expect(tool.schema.shape).not.toHaveProperty('param2');
+	});
+
+	it('should handle nested parameters correctly', () => {
+		const tool = createNodeAsTool(mockNode, mockCtx, mockNodeParameters);
+
+		expect(tool.schema.shape.nestedParam__subParam).toBeInstanceOf(z.ZodString);
+	});
+
+	it('should create a function that wraps the node execution', async () => {
+		const tool = createNodeAsTool(mockNode, mockCtx, mockNodeParameters);
+
+		const result = await tool.func({ param1: 'test value', nestedParam__subParam: 'nested value' });
+
+		expect(mockCtx.addInputData).toHaveBeenCalledWith(NodeConnectionType.AiTool, [
+			[{ json: { param1: 'test value', nestedParam__subParam: 'nested value' } }],
+		]);
+		expect(mockNode.execute).toHaveBeenCalled();
+		expect(mockCtx.addOutputData).toHaveBeenCalledWith(NodeConnectionType.AiTool, 0, [
+			[{ json: { response: [{ result: 'test' }] } }],
+		]);
+		expect(result).toBe(JSON.stringify([{ result: 'test' }]));
+	});
+});
diff --git a/packages/editor-ui/src/components/Node/NodeCreator/composables/useActionsGeneration.ts b/packages/editor-ui/src/components/Node/NodeCreator/composables/useActionsGeneration.ts
index 7385100141e85..85c306cdf6c5f 100644
--- a/packages/editor-ui/src/components/Node/NodeCreator/composables/useActionsGeneration.ts
+++ b/packages/editor-ui/src/components/Node/NodeCreator/composables/useActionsGeneration.ts
@@ -229,6 +229,7 @@ function resourceCategories(nodeTypeDescription: INodeTypeDescription): ActionTy
 export function useActionsGenerator() {
 	function generateNodeActions(node: INodeTypeDescription | undefined) {
 		if (!node) return [];
+		if (node.codex?.subcategories?.AI?.includes('Tools')) return [];
 		return [...triggersCategory(node), ...operationsCategory(node), ...resourceCategories(node)];
 	}
 	function filterActions(actions: ActionTypeDescription[]) {
diff --git a/packages/editor-ui/src/components/PersonalizationModal.test.ts b/packages/editor-ui/src/components/PersonalizationModal.test.ts
new file mode 100644
index 0000000000000..160beefccd617
--- /dev/null
+++ b/packages/editor-ui/src/components/PersonalizationModal.test.ts
@@ -0,0 +1,157 @@
+import userEvent from '@testing-library/user-event';
+import { createComponentRenderer } from '@/__tests__/render';
+import { getDropdownItems, mockedStore } from '@/__tests__/utils';
+import { createUser } from '@/__tests__/data/users';
+import { useSettingsStore } from '@/stores/settings.store';
+import PersonalizationModal from '@/components/PersonalizationModal.vue';
+import { useUsersStore } from '@/stores/users.store';
+import { createTestingPinia } from '@pinia/testing';
+import {
+	COMPANY_TYPE_KEY,
+	EMAIL_KEY,
+	COMPANY_INDUSTRY_EXTENDED_KEY,
+	OTHER_COMPANY_INDUSTRY_EXTENDED_KEY,
+	MARKETING_AUTOMATION_GOAL_KEY,
+	OTHER_MARKETING_AUTOMATION_GOAL_KEY,
+	ROLE_KEY,
+	ROLE_OTHER_KEY,
+	DEVOPS_AUTOMATION_GOAL_OTHER_KEY,
+	DEVOPS_AUTOMATION_GOAL_KEY,
+} from '@/constants';
+
+const renderModal = createComponentRenderer(PersonalizationModal, {
+	global: {
+		stubs: {
+			Modal: {
+				template: `
+					<div>
+						<slot name="header" />
+						<slot name="title" />
+						<slot name="content" />
+						<slot name="footer" />
+					</div>
+				`,
+			},
+		},
+	},
+});
+
+describe('PersonalizationModal', () => {
+	it('mounts', () => {
+		const { getByTitle } = renderModal({ pinia: createTestingPinia() });
+		expect(getByTitle('Customize n8n to you')).toBeInTheDocument();
+	});
+
+	it('shows user input when needed for desktop deployment', () => {
+		const pinia = createTestingPinia();
+		const usersStore = mockedStore(useUsersStore);
+		usersStore.currentUser = createUser({ firstName: undefined });
+
+		const settingsStore = mockedStore(useSettingsStore);
+		settingsStore.isDesktopDeployment = true;
+
+		const { getByTestId } = renderModal({ pinia });
+		expect(getByTestId(EMAIL_KEY)).toBeInTheDocument();
+	});
+
+	describe('Company field', () => {
+		it('allows completion of company related fields', async () => {
+			const { getByTestId } = renderModal({ pinia: createTestingPinia() });
+
+			const companyTypeSelect = getByTestId(COMPANY_TYPE_KEY);
+
+			const otherTypeOfCompanyOption = [...(await getDropdownItems(companyTypeSelect))].find(
+				(node) => node.textContent === 'Other',
+			) as Element;
+
+			await userEvent.click(otherTypeOfCompanyOption);
+
+			const industrySelect = getByTestId(COMPANY_INDUSTRY_EXTENDED_KEY);
+			expect(industrySelect).toBeInTheDocument();
+
+			const otherIndustryOption = [...(await getDropdownItems(industrySelect))].find(
+				(node) => node.textContent === 'Other (please specify)',
+			) as Element;
+
+			await userEvent.click(otherIndustryOption);
+
+			expect(getByTestId(OTHER_COMPANY_INDUSTRY_EXTENDED_KEY)).toBeInTheDocument();
+		});
+
+		it('shows only company and source select when not used for work', async () => {
+			const { getByTestId, baseElement } = renderModal({ pinia: createTestingPinia() });
+
+			const companyTypeSelect = getByTestId(COMPANY_TYPE_KEY);
+
+			const nonWorkOption = [...(await getDropdownItems(companyTypeSelect))].find(
+				(node) => node.textContent === "I'm not using n8n for work",
+			) as Element;
+
+			await userEvent.click(nonWorkOption);
+
+			expect(baseElement.querySelectorAll('input').length).toBe(2);
+		});
+	});
+
+	it('allows completion of role related fields', async () => {
+		const { getByTestId, queryByTestId } = renderModal({ pinia: createTestingPinia() });
+
+		const roleSelect = getByTestId(ROLE_KEY);
+		const roleItems = [...(await getDropdownItems(roleSelect))];
+
+		const devOps = roleItems.find((node) => node.textContent === 'Devops') as Element;
+		const engineering = roleItems.find((node) => node.textContent === 'Engineering') as Element;
+		const it = roleItems.find((node) => node.textContent === 'IT') as Element;
+		const other = roleItems.find(
+			(node) => node.textContent === 'Other (please specify)',
+		) as Element;
+
+		await userEvent.click(devOps);
+		const automationGoalSelect = getByTestId(DEVOPS_AUTOMATION_GOAL_KEY);
+		expect(automationGoalSelect).toBeInTheDocument();
+
+		await userEvent.click(engineering);
+		expect(automationGoalSelect).toBeInTheDocument();
+
+		await userEvent.click(it);
+		expect(automationGoalSelect).toBeInTheDocument();
+
+		const otherGoalsItem = [...(await getDropdownItems(automationGoalSelect))].find(
+			(node) => node.textContent === 'Other',
+		) as Element;
+
+		await userEvent.click(otherGoalsItem);
+		expect(getByTestId(DEVOPS_AUTOMATION_GOAL_OTHER_KEY)).toBeInTheDocument();
+
+		await userEvent.click(other);
+		expect(queryByTestId(DEVOPS_AUTOMATION_GOAL_KEY)).not.toBeInTheDocument();
+		expect(getByTestId(ROLE_OTHER_KEY)).toBeInTheDocument();
+	});
+
+	it('allows completion of marketing and sales related fields', async () => {
+		const { getByTestId } = renderModal({ pinia: createTestingPinia() });
+
+		const companyTypeSelect = getByTestId(COMPANY_TYPE_KEY);
+
+		const anyWorkOption = [...(await getDropdownItems(companyTypeSelect))].find(
+			(node) => node.textContent !== "I'm not using n8n for work",
+		) as Element;
+
+		await userEvent.click(anyWorkOption);
+
+		const roleSelect = getByTestId(ROLE_KEY);
+		const salesAndMarketingOption = [...(await getDropdownItems(roleSelect))].find(
+			(node) => node.textContent === 'Sales and Marketing',
+		) as Element;
+
+		await userEvent.click(salesAndMarketingOption);
+
+		const salesAndMarketingSelect = getByTestId(MARKETING_AUTOMATION_GOAL_KEY);
+		const otherItem = [...(await getDropdownItems(salesAndMarketingSelect))].find(
+			(node) => node.textContent === 'Other',
+		) as Element;
+
+		await userEvent.click(otherItem);
+		expect(getByTestId(OTHER_MARKETING_AUTOMATION_GOAL_KEY)).toBeInTheDocument();
+	});
+});
diff --git a/packages/editor-ui/src/components/PersonalizationModal.vue b/packages/editor-ui/src/components/PersonalizationModal.vue
index 13e360c3c1981..1eab5e4b20816 100644
--- a/packages/editor-ui/src/components/PersonalizationModal.vue
+++ b/packages/editor-ui/src/components/PersonalizationModal.vue
@@ -1,6 +1,5 @@
-<script lang="ts">
-import { defineComponent } from 'vue';
-import { mapStores } from 'pinia';
+<script lang="ts" setup>
+import { computed, ref } from 'vue';
 import {
 	COMPANY_SIZE_100_499,
 	COMPANY_SIZE_1000_OR_MORE,
@@ -86,617 +85,513 @@ import {
 import { useToast } from '@/composables/useToast';
 import Modal from '@/components/Modal.vue';
 import type { IFormInputs, IPersonalizationLatestVersion } from '@/Interface';
-import { useUIStore } from '@/stores/ui.store';
 import { useSettingsStore } from '@/stores/settings.store';
 import { useRootStore } from '@/stores/root.store';
 import { useUsersStore } from '@/stores/users.store';
 import { createEventBus, createFormEventBus } from 'n8n-design-system/utils';
 import { usePostHog } from '@/stores/posthog.store';
 import { useExternalHooks } from '@/composables/useExternalHooks';
-import { useUsageStore } from '@/stores/usage.store';
-import { useMessage } from '@/composables/useMessage';
+import { useI18n } from '@/composables/useI18n';
+import { useRoute, useRouter } from 'vue-router';
 
 const SURVEY_VERSION = 'v4';
 
-export default defineComponent({
-	name: 'PersonalizationModal',
-	components: { Modal },
-	props: {
-		teleported: {
-			type: Boolean,
-			default: true,
+const externalHooks = useExternalHooks();
+const modalBus = createEventBus();
+const formBus = createFormEventBus();
+const { showError } = useToast();
+const i18n = useI18n();
+const rootStore = useRootStore();
+const settingsStore = useSettingsStore();
+const usersStore = useUsersStore();
+const posthogStore = usePostHog();
+const route = useRoute();
+const router = useRouter();
+
+const formValues = ref<Record<string, string>>({});
+const isSaving = ref(false);
+
+const survey = computed<IFormInputs>(() => [
+	{
+		name: EMAIL_KEY,
+		properties: {
+			label: i18n.baseText('personalizationModal.yourEmailAddress'),
+			type: 'text',
+			placeholder: i18n.baseText('personalizationModal.email'),
 		},
+		shouldDisplay: () => settingsStore.isDesktopDeployment && !usersStore.currentUser?.firstName,
 	},
-	setup() {
-		const externalHooks = useExternalHooks();
+	{
+		name: COMPANY_TYPE_KEY,
+		properties: {
+			label: i18n.baseText('personalizationModal.whatBestDescribesYourCompany'),
+			type: 'select',
+			placeholder: i18n.baseText('personalizationModal.select'),
+			options: [
+				{
+					label: i18n.baseText('personalizationModal.saas'),
+					value: SAAS_COMPANY_TYPE,
+				},
+				{
+					label: i18n.baseText('personalizationModal.eCommerce'),
+					value: ECOMMERCE_COMPANY_TYPE,
+				},
 
-		return {
-			externalHooks,
-			...useToast(),
-			...useMessage(),
-		};
+				{
+					label: i18n.baseText('personalizationModal.digitalAgencyOrConsultant'),
+					value: DIGITAL_AGENCY_COMPANY_TYPE,
+				},
+				{
+					label: i18n.baseText('personalizationModal.systemsIntegrator'),
+					value: SYSTEMS_INTEGRATOR_COMPANY_TYPE,
+				},
+				{
+					value: EDUCATION_TYPE,
+					label: i18n.baseText('personalizationModal.education'),
+				},
+				{
+					label: i18n.baseText('personalizationModal.other'),
+					value: OTHER_COMPANY_TYPE,
+				},
+				{
+					label: i18n.baseText('personalizationModal.imNotUsingN8nForWork'),
+					value: PERSONAL_COMPANY_TYPE,
+				},
+			],
+		},
 	},
-	data() {
-		return {
-			formValues: {} as Record<string, string>,
-			isSaving: false,
-			PERSONALIZATION_MODAL_KEY,
-			otherWorkAreaFieldVisible: false,
-			otherCompanyIndustryFieldVisible: false,
-			showAllIndustryQuestions: true,
-			registerForEnterpriseTrial: false,
-			modalBus: createEventBus(),
-			formBus: createFormEventBus(),
-			domainBlocklist: [] as string[],
-		};
+	{
+		name: COMPANY_INDUSTRY_EXTENDED_KEY,
+		properties: {
+			type: 'multi-select',
+			label: i18n.baseText('personalizationModal.whichIndustriesIsYourCompanyIn'),
+			placeholder: i18n.baseText('personalizationModal.select'),
+			options: [
+				{
+					value: FINANCE_INSURANCE_INDUSTRY,
+					label: i18n.baseText('personalizationModal.financeOrInsurance'),
+				},
+				{
+					value: GOVERNMENT_INDUSTRY,
+					label: i18n.baseText('personalizationModal.government'),
+				},
+				{
+					value: HEALTHCARE_INDUSTRY,
+					label: i18n.baseText('personalizationModal.healthcare'),
+				},
+				{
+					value: IT_INDUSTRY,
+					label: i18n.baseText('personalizationModal.it'),
+				},
+				{
+					value: LEGAL_INDUSTRY,
+					label: i18n.baseText('personalizationModal.legal'),
+				},
+				{
+					value: MSP_INDUSTRY,
+					label: i18n.baseText('personalizationModal.managedServiceProvider'),
+				},
+				{
+					value: MARKETING_INDUSTRY,
+					label: i18n.baseText('personalizationModal.marketing'),
+				},
+				{
+					value: MEDIA_INDUSTRY,
+					label: i18n.baseText('personalizationModal.media'),
+				},
+				{
+					value: MANUFACTURING_INDUSTRY,
+					label: i18n.baseText('personalizationModal.manufacturing'),
+				},
+				{
+					value: PHYSICAL_RETAIL_OR_SERVICES,
+					label: i18n.baseText('personalizationModal.physicalRetailOrServices'),
+				},
+				{
+					value: REAL_ESTATE_OR_CONSTRUCTION,
+					label: i18n.baseText('personalizationModal.realEstateOrConstruction'),
+				},
+				{
+					value: SECURITY_INDUSTRY,
+					label: i18n.baseText('personalizationModal.security'),
+				},
+				{
+					value: TELECOMS_INDUSTRY,
+					label: i18n.baseText('personalizationModal.telecoms'),
+				},
+				{
+					value: OTHER_INDUSTRY_OPTION,
+					label: i18n.baseText('personalizationModal.otherPleaseSpecify'),
+				},
+			],
+		},
+		shouldDisplay(values): boolean {
+			const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY];
+			return companyType === OTHER_COMPANY_TYPE;
+		},
 	},
-	computed: {
-		...mapStores(
-			useRootStore,
-			useSettingsStore,
-			useUIStore,
-			useUsersStore,
-			useUsageStore,
-			usePostHog,
-		),
-		currentUser() {
-			return this.usersStore.currentUser;
+	{
+		name: OTHER_COMPANY_INDUSTRY_EXTENDED_KEY,
+		properties: {
+			placeholder: i18n.baseText('personalizationModal.specifyYourCompanysIndustry'),
 		},
-		canRegisterForEnterpriseTrial() {
-			if (
-				this.settingsStore.isCloudDeployment ||
-				this.domainBlocklist.length === 0 ||
-				!this.currentUser?.email
-			) {
-				return false;
-			}
-
-			const isSizeEligible = [COMPANY_SIZE_500_999, COMPANY_SIZE_1000_OR_MORE].includes(
-				this.formValues[COMPANY_SIZE_KEY],
+		shouldDisplay(values): boolean {
+			const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY];
+			const companyIndustry = (values as IPersonalizationLatestVersion)[
+				COMPANY_INDUSTRY_EXTENDED_KEY
+			];
+			return (
+				companyType === OTHER_COMPANY_TYPE &&
+				!!companyIndustry &&
+				companyIndustry.includes(OTHER_INDUSTRY_OPTION)
 			);
-
-			const emailParts = this.currentUser.email.split('@');
-			const emailDomain = emailParts[emailParts.length - 1];
-			const isEmailEligible = !this.domainBlocklist.find(
-				(blocklistedDomain) => emailDomain === blocklistedDomain,
+		},
+	},
+	{
+		name: ROLE_KEY,
+		properties: {
+			type: 'select',
+			label: i18n.baseText('personalizationModal.whichRoleBestDescribesYou'),
+			placeholder: i18n.baseText('personalizationModal.select'),
+			options: [
+				{
+					value: ROLE_BUSINESS_OWNER,
+					label: i18n.baseText('personalizationModal.businessOwner'),
+				},
+				{
+					value: ROLE_CUSTOMER_SUPPORT,
+					label: i18n.baseText('personalizationModal.customerSupport'),
+				},
+				{
+					value: ROLE_DATA_SCIENCE,
+					label: i18n.baseText('personalizationModal.dataScience'),
+				},
+				{
+					value: ROLE_DEVOPS,
+					label: i18n.baseText('personalizationModal.devops'),
+				},
+				{
+					value: ROLE_IT,
+					label: i18n.baseText('personalizationModal.it'),
+				},
+				{
+					value: ROLE_ENGINEERING,
+					label: i18n.baseText('personalizationModal.engineering'),
+				},
+				{
+					value: ROLE_SALES_AND_MARKETING,
+					label: i18n.baseText('personalizationModal.salesAndMarketing'),
+				},
+				{
+					value: ROLE_SECURITY,
+					label: i18n.baseText('personalizationModal.security'),
+				},
+				{
+					value: ROLE_OTHER,
+					label: i18n.baseText('personalizationModal.otherPleaseSpecify'),
+				},
+			],
+		},
+		shouldDisplay(values): boolean {
+			const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY];
+			return companyType !== PERSONAL_COMPANY_TYPE;
+		},
+	},
+	{
+		name: ROLE_OTHER_KEY,
+		properties: {
+			placeholder: i18n.baseText('personalizationModal.specifyYourRole'),
+		},
+		shouldDisplay(values): boolean {
+			const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY];
+			const role = (values as IPersonalizationLatestVersion)[ROLE_KEY];
+			return companyType !== PERSONAL_COMPANY_TYPE && role === ROLE_OTHER;
+		},
+	},
+	{
+		name: DEVOPS_AUTOMATION_GOAL_KEY,
+		properties: {
+			type: 'multi-select',
+			label: i18n.baseText('personalizationModal.whatAreYouLookingToAutomate'),
+			placeholder: i18n.baseText('personalizationModal.select'),
+			options: [
+				{
+					value: DEVOPS_AUTOMATION_CI_CD_GOAL,
+					label: i18n.baseText('personalizationModal.cicd'),
+				},
+				{
+					value: DEVOPS_AUTOMATION_CLOUD_INFRASTRUCTURE_ORCHESTRATION_GOAL,
+					label: i18n.baseText('personalizationModal.cloudInfrastructureOrchestration'),
+				},
+				{
+					value: DEVOPS_AUTOMATION_DATA_SYNCING_GOAL,
+					label: i18n.baseText('personalizationModal.dataSynching'),
+				},
+				{
+					value: DEVOPS_INCIDENT_RESPONSE_GOAL,
+					label: i18n.baseText('personalizationModal.incidentResponse'),
+				},
+				{
+					value: DEVOPS_MONITORING_AND_ALERTING_GOAL,
+					label: i18n.baseText('personalizationModal.monitoringAndAlerting'),
+				},
+				{
+					value: DEVOPS_REPORTING_GOAL,
+					label: i18n.baseText('personalizationModal.reporting'),
+				},
+				{
+					value: DEVOPS_TICKETING_SYSTEMS_INTEGRATIONS_GOAL,
+					label: i18n.baseText('personalizationModal.ticketingSystemsIntegrations'),
+				},
+				{
+					value: OTHER_AUTOMATION_GOAL,
+					label: i18n.baseText('personalizationModal.other'),
+				},
+			],
+		},
+		shouldDisplay(values): boolean {
+			const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY];
+			const role = (values as IPersonalizationLatestVersion)[ROLE_KEY] as string;
+			return (
+				companyType !== PERSONAL_COMPANY_TYPE &&
+				[ROLE_DEVOPS, ROLE_ENGINEERING, ROLE_IT].includes(role)
 			);
-
-			return isSizeEligible && isEmailEligible;
 		},
-		survey() {
-			const survey: IFormInputs = [
-				{
-					name: EMAIL_KEY,
-					properties: {
-						label: this.$locale.baseText('personalizationModal.yourEmailAddress'),
-						type: 'text',
-						placeholder: this.$locale.baseText('personalizationModal.email'),
-					},
-					shouldDisplay: () =>
-						this.settingsStore.isDesktopDeployment && !this.usersStore.currentUser?.firstName,
-				},
-				{
-					name: COMPANY_TYPE_KEY,
-					properties: {
-						label: this.$locale.baseText('personalizationModal.whatBestDescribesYourCompany'),
-						type: 'select',
-						placeholder: this.$locale.baseText('personalizationModal.select'),
-						options: [
-							{
-								label: this.$locale.baseText('personalizationModal.saas'),
-								value: SAAS_COMPANY_TYPE,
-							},
-							{
-								label: this.$locale.baseText('personalizationModal.eCommerce'),
-								value: ECOMMERCE_COMPANY_TYPE,
-							},
-
-							{
-								label: this.$locale.baseText('personalizationModal.digitalAgencyOrConsultant'),
-								value: DIGITAL_AGENCY_COMPANY_TYPE,
-							},
-							{
-								label: this.$locale.baseText('personalizationModal.systemsIntegrator'),
-								value: SYSTEMS_INTEGRATOR_COMPANY_TYPE,
-							},
-							{
-								value: EDUCATION_TYPE,
-								label: this.$locale.baseText('personalizationModal.education'),
-							},
-							{
-								label: this.$locale.baseText('personalizationModal.other'),
-								value: OTHER_COMPANY_TYPE,
-							},
-							{
-								label: this.$locale.baseText('personalizationModal.imNotUsingN8nForWork'),
-								value: PERSONAL_COMPANY_TYPE,
-							},
-						],
-					},
-				},
-				{
-					name: COMPANY_INDUSTRY_EXTENDED_KEY,
-					properties: {
-						type: 'multi-select',
-						label: this.$locale.baseText('personalizationModal.whichIndustriesIsYourCompanyIn'),
-						placeholder: this.$locale.baseText('personalizationModal.select'),
-						options: [
-							{
-								value: FINANCE_INSURANCE_INDUSTRY,
-								label: this.$locale.baseText('personalizationModal.financeOrInsurance'),
-							},
-							{
-								value: GOVERNMENT_INDUSTRY,
-								label: this.$locale.baseText('personalizationModal.government'),
-							},
-							{
-								value: HEALTHCARE_INDUSTRY,
-								label: this.$locale.baseText('personalizationModal.healthcare'),
-							},
-							{
-								value: IT_INDUSTRY,
-								label: this.$locale.baseText('personalizationModal.it'),
-							},
-							{
-								value: LEGAL_INDUSTRY,
-								label: this.$locale.baseText('personalizationModal.legal'),
-							},
-							{
-								value: MSP_INDUSTRY,
-								label: this.$locale.baseText('personalizationModal.managedServiceProvider'),
-							},
-							{
-								value: MARKETING_INDUSTRY,
-								label: this.$locale.baseText('personalizationModal.marketing'),
-							},
-							{
-								value: MEDIA_INDUSTRY,
-								label: this.$locale.baseText('personalizationModal.media'),
-							},
-							{
-								value: MANUFACTURING_INDUSTRY,
-								label: this.$locale.baseText('personalizationModal.manufacturing'),
-							},
-							{
-								value: PHYSICAL_RETAIL_OR_SERVICES,
-								label: this.$locale.baseText('personalizationModal.physicalRetailOrServices'),
-							},
-							{
-								value: REAL_ESTATE_OR_CONSTRUCTION,
-								label: this.$locale.baseText('personalizationModal.realEstateOrConstruction'),
-							},
-							{
-								value: SECURITY_INDUSTRY,
-								label: this.$locale.baseText('personalizationModal.security'),
-							},
-							{
-								value: TELECOMS_INDUSTRY,
-								label: this.$locale.baseText('personalizationModal.telecoms'),
-							},
-							{
-								value: OTHER_INDUSTRY_OPTION,
-								label: this.$locale.baseText('personalizationModal.otherPleaseSpecify'),
-							},
-						],
-					},
-					shouldDisplay(values): boolean {
-						const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY];
-						return companyType === OTHER_COMPANY_TYPE;
-					},
-				},
-				{
-					name: OTHER_COMPANY_INDUSTRY_EXTENDED_KEY,
-					properties: {
-						placeholder: this.$locale.baseText('personalizationModal.specifyYourCompanysIndustry'),
-					},
-					shouldDisplay(values): boolean {
-						const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY];
-						const companyIndustry = (values as IPersonalizationLatestVersion)[
-							COMPANY_INDUSTRY_EXTENDED_KEY
-						];
-						return (
-							companyType === OTHER_COMPANY_TYPE &&
-							!!companyIndustry &&
-							companyIndustry.includes(OTHER_INDUSTRY_OPTION)
-						);
-					},
-				},
-				{
-					name: ROLE_KEY,
-					properties: {
-						type: 'select',
-						label: this.$locale.baseText('personalizationModal.whichRoleBestDescribesYou'),
-						placeholder: this.$locale.baseText('personalizationModal.select'),
-						options: [
-							{
-								value: ROLE_BUSINESS_OWNER,
-								label: this.$locale.baseText('personalizationModal.businessOwner'),
-							},
-							{
-								value: ROLE_CUSTOMER_SUPPORT,
-								label: this.$locale.baseText('personalizationModal.customerSupport'),
-							},
-							{
-								value: ROLE_DATA_SCIENCE,
-								label: this.$locale.baseText('personalizationModal.dataScience'),
-							},
-							{
-								value: ROLE_DEVOPS,
-								label: this.$locale.baseText('personalizationModal.devops'),
-							},
-							{
-								value: ROLE_IT,
-								label: this.$locale.baseText('personalizationModal.it'),
-							},
-							{
-								value: ROLE_ENGINEERING,
-								label: this.$locale.baseText('personalizationModal.engineering'),
-							},
-							{
-								value: ROLE_SALES_AND_MARKETING,
-								label: this.$locale.baseText('personalizationModal.salesAndMarketing'),
-							},
-							{
-								value: ROLE_SECURITY,
-								label: this.$locale.baseText('personalizationModal.security'),
-							},
-							{
-								value: ROLE_OTHER,
-								label: this.$locale.baseText('personalizationModal.otherPleaseSpecify'),
-							},
-						],
-					},
-					shouldDisplay(values): boolean {
-						const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY];
-						return companyType !== PERSONAL_COMPANY_TYPE;
-					},
-				},
-				{
-					name: ROLE_OTHER_KEY,
-					properties: {
-						placeholder: this.$locale.baseText('personalizationModal.specifyYourRole'),
-					},
-					shouldDisplay(values): boolean {
-						const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY];
-						const role = (values as IPersonalizationLatestVersion)[ROLE_KEY];
-						return companyType !== PERSONAL_COMPANY_TYPE && role === ROLE_OTHER;
-					},
-				},
-				{
-					name: DEVOPS_AUTOMATION_GOAL_KEY,
-					properties: {
-						type: 'multi-select',
-						label: this.$locale.baseText('personalizationModal.whatAreYouLookingToAutomate'),
-						placeholder: this.$locale.baseText('personalizationModal.select'),
-						options: [
-							{
-								value: DEVOPS_AUTOMATION_CI_CD_GOAL,
-								label: this.$locale.baseText('personalizationModal.cicd'),
-							},
-							{
-								value: DEVOPS_AUTOMATION_CLOUD_INFRASTRUCTURE_ORCHESTRATION_GOAL,
-								label: this.$locale.baseText(
-									'personalizationModal.cloudInfrastructureOrchestration',
-								),
-							},
-							{
-								value: DEVOPS_AUTOMATION_DATA_SYNCING_GOAL,
-								label: this.$locale.baseText('personalizationModal.dataSynching'),
-							},
-							{
-								value: DEVOPS_INCIDENT_RESPONSE_GOAL,
-								label: this.$locale.baseText('personalizationModal.incidentResponse'),
-							},
-							{
-								value: DEVOPS_MONITORING_AND_ALERTING_GOAL,
-								label: this.$locale.baseText('personalizationModal.monitoringAndAlerting'),
-							},
-							{
-								value: DEVOPS_REPORTING_GOAL,
-								label: this.$locale.baseText('personalizationModal.reporting'),
-							},
-							{
-								value: DEVOPS_TICKETING_SYSTEMS_INTEGRATIONS_GOAL,
-								label: this.$locale.baseText('personalizationModal.ticketingSystemsIntegrations'),
-							},
-							{
-								value: OTHER_AUTOMATION_GOAL,
-								label: this.$locale.baseText('personalizationModal.other'),
-							},
-						],
-					},
-					shouldDisplay(values): boolean {
-						const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY];
-						const role = (values as IPersonalizationLatestVersion)[ROLE_KEY] as string;
-						return (
-							companyType !== PERSONAL_COMPANY_TYPE &&
-							[ROLE_DEVOPS, ROLE_ENGINEERING, ROLE_IT].includes(role)
-						);
-					},
-				},
-				{
-					name: DEVOPS_AUTOMATION_GOAL_OTHER_KEY,
-					properties: {
-						placeholder: this.$locale.baseText('personalizationModal.specifyYourAutomationGoal'),
-					},
-					shouldDisplay(values): boolean {
-						const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY];
-						const goals = (values as IPersonalizationLatestVersion)[DEVOPS_AUTOMATION_GOAL_KEY];
-						const role = (values as IPersonalizationLatestVersion)[ROLE_KEY] as string;
-						return (
-							companyType !== PERSONAL_COMPANY_TYPE &&
-							[ROLE_DEVOPS, ROLE_ENGINEERING, ROLE_IT].includes(role) &&
-							!!goals &&
-							goals.includes(DEVOPS_AUTOMATION_OTHER)
-						);
-					},
-				},
-				{
-					name: MARKETING_AUTOMATION_GOAL_KEY,
-					properties: {
-						type: 'multi-select',
-						label: this.$locale.baseText('personalizationModal.specifySalesMarketingGoal'),
-						placeholder: this.$locale.baseText('personalizationModal.select'),
-						options: [
-							{
-								label: this.$locale.baseText('personalizationModal.leadGeneration'),
-								value: MARKETING_AUTOMATION_LEAD_GENERATION_GOAL,
-							},
-							{
-								label: this.$locale.baseText('personalizationModal.customerCommunication'),
-								value: MARKETING_AUTOMATION_CUSTOMER_COMMUNICATION,
-							},
-							{
-								label: this.$locale.baseText('personalizationModal.customerActions'),
-								value: MARKETING_AUTOMATION_ACTIONS,
-							},
-							{
-								label: this.$locale.baseText('personalizationModal.adCampaign'),
-								value: MARKETING_AUTOMATION_AD_CAMPAIGN,
-							},
-							{
-								label: this.$locale.baseText('personalizationModal.reporting'),
-								value: MARKETING_AUTOMATION_REPORTING,
-							},
-							{
-								label: this.$locale.baseText('personalizationModal.dataSynching'),
-								value: MARKETING_AUTOMATION_DATA_SYNCHING,
-							},
-							{
-								label: this.$locale.baseText('personalizationModal.other'),
-								value: MARKETING_AUTOMATION_OTHER,
-							},
-						],
-					},
-					shouldDisplay(values): boolean {
-						const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY];
-						const role = (values as IPersonalizationLatestVersion)[ROLE_KEY];
-						return companyType !== PERSONAL_COMPANY_TYPE && role === ROLE_SALES_AND_MARKETING;
-					},
-				},
-				{
-					name: OTHER_MARKETING_AUTOMATION_GOAL_KEY,
-					properties: {
-						placeholder: this.$locale.baseText(
-							'personalizationModal.specifyOtherSalesAndMarketingGoal',
-						),
-					},
-					shouldDisplay(values): boolean {
-						const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY];
-						const goals = (values as IPersonalizationLatestVersion)[MARKETING_AUTOMATION_GOAL_KEY];
-						const role = (values as IPersonalizationLatestVersion)[ROLE_KEY];
-						return (
-							companyType !== PERSONAL_COMPANY_TYPE &&
-							role === ROLE_SALES_AND_MARKETING &&
-							!!goals &&
-							goals.includes(MARKETING_AUTOMATION_OTHER)
-						);
-					},
-				},
-				{
-					name: AUTOMATION_BENEFICIARY_KEY,
-					properties: {
-						type: 'select',
-						label: this.$locale.baseText('personalizationModal.specifyAutomationBeneficiary'),
-						placeholder: this.$locale.baseText('personalizationModal.select'),
-						options: [
-							{
-								label: this.$locale.baseText('personalizationModal.myself'),
-								value: AUTOMATION_BENEFICIARY_SELF,
-							},
-							{
-								label: this.$locale.baseText('personalizationModal.myTeam'),
-								value: AUTOMATION_BENEFICIARY_MY_TEAM,
-							},
-							{
-								label: this.$locale.baseText('personalizationModal.otherTeams'),
-								value: AUTOMATION_BENEFICIARY_OTHER_TEAMS,
-							},
-						],
-					},
-					shouldDisplay(values): boolean {
-						const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY];
-						return companyType !== PERSONAL_COMPANY_TYPE;
-					},
-				},
-				{
-					name: COMPANY_SIZE_KEY,
-					properties: {
-						type: 'select',
-						label: this.$locale.baseText('personalizationModal.howBigIsYourCompany'),
-						placeholder: this.$locale.baseText('personalizationModal.select'),
-						options: [
-							{
-								label: this.$locale.baseText('personalizationModal.lessThan20People'),
-								value: COMPANY_SIZE_20_OR_LESS,
-							},
-							{
-								label: `20-99 ${this.$locale.baseText('personalizationModal.people')}`,
-								value: COMPANY_SIZE_20_99,
-							},
-							{
-								label: `100-499 ${this.$locale.baseText('personalizationModal.people')}`,
-								value: COMPANY_SIZE_100_499,
-							},
-							{
-								label: `500-999 ${this.$locale.baseText('personalizationModal.people')}`,
-								value: COMPANY_SIZE_500_999,
-							},
-							{
-								label: `1000+ ${this.$locale.baseText('personalizationModal.people')}`,
-								value: COMPANY_SIZE_1000_OR_MORE,
-							},
-							{
-								label: this.$locale.baseText('personalizationModal.imNotUsingN8nForWork'),
-								value: COMPANY_SIZE_PERSONAL_USE,
-							},
-						],
-					},
-					shouldDisplay(values): boolean {
-						const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY];
-						return companyType !== PERSONAL_COMPANY_TYPE;
-					},
-				},
-				{
-					name: REPORTED_SOURCE_KEY,
-					properties: {
-						type: 'select',
-						label: this.$locale.baseText('personalizationModal.howDidYouHearAboutN8n'),
-						placeholder: this.$locale.baseText('personalizationModal.select'),
-						options: [
-							{
-								label: 'Google',
-								value: REPORTED_SOURCE_GOOGLE,
-							},
-							{
-								label: 'Twitter',
-								value: REPORTED_SOURCE_TWITTER,
-							},
-							{
-								label: 'LinkedIn',
-								value: REPORTED_SOURCE_LINKEDIN,
-							},
-							{
-								label: 'YouTube',
-								value: REPORTED_SOURCE_YOUTUBE,
-							},
-							{
-								label: this.$locale.baseText('personalizationModal.friendWordOfMouth'),
-								value: REPORTED_SOURCE_FRIEND,
-							},
-							{
-								label: this.$locale.baseText('personalizationModal.podcast'),
-								value: REPORTED_SOURCE_PODCAST,
-							},
-							{
-								label: this.$locale.baseText('personalizationModal.event'),
-								value: REPORTED_SOURCE_EVENT,
-							},
-							{
-								label: this.$locale.baseText('personalizationModal.otherPleaseSpecify'),
-								value: REPORTED_SOURCE_OTHER,
-							},
-						],
-					},
-				},
-				{
-					name: REPORTED_SOURCE_OTHER_KEY,
-					properties: {
-						placeholder: this.$locale.baseText('personalizationModal.specifyReportedSource'),
-					},
-					shouldDisplay(values): boolean {
-						const reportedSource = (values as IPersonalizationLatestVersion)[REPORTED_SOURCE_KEY];
-						return reportedSource === REPORTED_SOURCE_OTHER;
-					},
+	},
+	{
+		name: DEVOPS_AUTOMATION_GOAL_OTHER_KEY,
+		properties: {
+			placeholder: i18n.baseText('personalizationModal.specifyYourAutomationGoal'),
+		},
+		shouldDisplay(values): boolean {
+			const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY];
+			const goals = (values as IPersonalizationLatestVersion)[DEVOPS_AUTOMATION_GOAL_KEY];
+			const role = (values as IPersonalizationLatestVersion)[ROLE_KEY] as string;
+			return (
+				companyType !== PERSONAL_COMPANY_TYPE &&
+				[ROLE_DEVOPS, ROLE_ENGINEERING, ROLE_IT].includes(role) &&
+				!!goals &&
+				goals.includes(DEVOPS_AUTOMATION_OTHER)
+			);
+		},
+	},
+	{
+		name: MARKETING_AUTOMATION_GOAL_KEY,
+		properties: {
+			type: 'multi-select',
+			label: i18n.baseText('personalizationModal.specifySalesMarketingGoal'),
+			placeholder: i18n.baseText('personalizationModal.select'),
+			options: [
+				{
+					label: i18n.baseText('personalizationModal.leadGeneration'),
+					value: MARKETING_AUTOMATION_LEAD_GENERATION_GOAL,
 				},
-			];
-
-			return survey;
+				{
+					label: i18n.baseText('personalizationModal.customerCommunication'),
+					value: MARKETING_AUTOMATION_CUSTOMER_COMMUNICATION,
+				},
+				{
+					label: i18n.baseText('personalizationModal.customerActions'),
+					value: MARKETING_AUTOMATION_ACTIONS,
+				},
+				{
+					label: i18n.baseText('personalizationModal.adCampaign'),
+					value: MARKETING_AUTOMATION_AD_CAMPAIGN,
+				},
+				{
+					label: i18n.baseText('personalizationModal.reporting'),
+					value: MARKETING_AUTOMATION_REPORTING,
+				},
+				{
+					label: i18n.baseText('personalizationModal.dataSynching'),
+					value: MARKETING_AUTOMATION_DATA_SYNCHING,
+				},
+				{
+					label: i18n.baseText('personalizationModal.other'),
+					value: MARKETING_AUTOMATION_OTHER,
+				},
+			],
+		},
+		shouldDisplay(values): boolean {
+			const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY];
+			const role = (values as IPersonalizationLatestVersion)[ROLE_KEY];
+			return companyType !== PERSONAL_COMPANY_TYPE && role === ROLE_SALES_AND_MARKETING;
+		},
+	},
+	{
+		name: OTHER_MARKETING_AUTOMATION_GOAL_KEY,
+		properties: {
+			placeholder: i18n.baseText('personalizationModal.specifyOtherSalesAndMarketingGoal'),
+		},
+		shouldDisplay(values): boolean {
+			const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY];
+			const goals = (values as IPersonalizationLatestVersion)[MARKETING_AUTOMATION_GOAL_KEY];
+			const role = (values as IPersonalizationLatestVersion)[ROLE_KEY];
+			return (
+				companyType !== PERSONAL_COMPANY_TYPE &&
+				role === ROLE_SALES_AND_MARKETING &&
+				!!goals &&
+				goals.includes(MARKETING_AUTOMATION_OTHER)
+			);
+		},
+	},
+	{
+		name: AUTOMATION_BENEFICIARY_KEY,
+		properties: {
+			type: 'select',
+			label: i18n.baseText('personalizationModal.specifyAutomationBeneficiary'),
+			placeholder: i18n.baseText('personalizationModal.select'),
+			options: [
+				{
+					label: i18n.baseText('personalizationModal.myself'),
+					value: AUTOMATION_BENEFICIARY_SELF,
+				},
+				{
+					label: i18n.baseText('personalizationModal.myTeam'),
+					value: AUTOMATION_BENEFICIARY_MY_TEAM,
+				},
+				{
+					label: i18n.baseText('personalizationModal.otherTeams'),
+					value: AUTOMATION_BENEFICIARY_OTHER_TEAMS,
+				},
+			],
+		},
+		shouldDisplay(values): boolean {
+			const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY];
+			return companyType !== PERSONAL_COMPANY_TYPE;
 		},
 	},
-	mounted() {
-		void this.loadDomainBlocklist();
+	{
+		name: COMPANY_SIZE_KEY,
+		properties: {
+			type: 'select',
+			label: i18n.baseText('personalizationModal.howBigIsYourCompany'),
+			placeholder: i18n.baseText('personalizationModal.select'),
+			options: [
+				{
+					label: i18n.baseText('personalizationModal.lessThan20People'),
+					value: COMPANY_SIZE_20_OR_LESS,
+				},
+				{
+					label: `20-99 ${i18n.baseText('personalizationModal.people')}`,
+					value: COMPANY_SIZE_20_99,
+				},
+				{
+					label: `100-499 ${i18n.baseText('personalizationModal.people')}`,
+					value: COMPANY_SIZE_100_499,
+				},
+				{
+					label: `500-999 ${i18n.baseText('personalizationModal.people')}`,
+					value: COMPANY_SIZE_500_999,
+				},
+				{
+					label: `1000+ ${i18n.baseText('personalizationModal.people')}`,
+					value: COMPANY_SIZE_1000_OR_MORE,
+				},
+				{
+					label: i18n.baseText('personalizationModal.imNotUsingN8nForWork'),
+					value: COMPANY_SIZE_PERSONAL_USE,
+				},
+			],
+		},
+		shouldDisplay(values): boolean {
+			const companyType = (values as IPersonalizationLatestVersion)[COMPANY_TYPE_KEY];
+			return companyType !== PERSONAL_COMPANY_TYPE;
+		},
 	},
-	methods: {
-		closeDialog() {
-			this.modalBus.emit('close');
-			const isPartOfOnboardingExperiment =
-				this.posthogStore.getVariant(MORE_ONBOARDING_OPTIONS_EXPERIMENT.name) ===
-				MORE_ONBOARDING_OPTIONS_EXPERIMENT.control;
-			// In case the redirect to homepage for new users didn't happen
-			// we try again after closing the modal
-			if (this.$route.name !== VIEWS.HOMEPAGE && !isPartOfOnboardingExperiment) {
-				void this.$router.replace({ name: VIEWS.HOMEPAGE });
-			}
+	{
+		name: REPORTED_SOURCE_KEY,
+		properties: {
+			type: 'select',
+			label: i18n.baseText('personalizationModal.howDidYouHearAboutN8n'),
+			placeholder: i18n.baseText('personalizationModal.select'),
+			options: [
+				{
+					label: 'Google',
+					value: REPORTED_SOURCE_GOOGLE,
+				},
+				{
+					label: 'Twitter',
+					value: REPORTED_SOURCE_TWITTER,
+				},
+				{
+					label: 'LinkedIn',
+					value: REPORTED_SOURCE_LINKEDIN,
+				},
+				{
+					label: 'YouTube',
+					value: REPORTED_SOURCE_YOUTUBE,
+				},
+				{
+					label: i18n.baseText('personalizationModal.friendWordOfMouth'),
+					value: REPORTED_SOURCE_FRIEND,
+				},
+				{
+					label: i18n.baseText('personalizationModal.podcast'),
+					value: REPORTED_SOURCE_PODCAST,
+				},
+				{
+					label: i18n.baseText('personalizationModal.event'),
+					value: REPORTED_SOURCE_EVENT,
+				},
+				{
+					label: i18n.baseText('personalizationModal.otherPleaseSpecify'),
+					value: REPORTED_SOURCE_OTHER,
+				},
+			],
 		},
-		async loadDomainBlocklist() {
-			try {
-				this.domainBlocklist = (await import('email-providers/common.json')).default;
-			} catch (error) {}
+	},
+	{
+		name: REPORTED_SOURCE_OTHER_KEY,
+		properties: {
+			placeholder: i18n.baseText('personalizationModal.specifyReportedSource'),
 		},
-		onSave() {
-			this.formBus.emit('submit');
+		shouldDisplay(values): boolean {
+			const reportedSource = (values as IPersonalizationLatestVersion)[REPORTED_SOURCE_KEY];
+			return reportedSource === REPORTED_SOURCE_OTHER;
 		},
-		async onSubmit(values: IPersonalizationLatestVersion): Promise<void> {
-			this.isSaving = true;
-
-			try {
-				const survey: IPersonalizationLatestVersion = {
-					...values,
-					version: SURVEY_VERSION,
-					personalization_survey_submitted_at: new Date().toISOString(),
-					personalization_survey_n8n_version: this.rootStore.versionCli,
-				};
+	},
+]);
 
-				await this.externalHooks.run('personalizationModal.onSubmit', survey);
+const onSave = () => {
+	formBus.emit('submit');
+};
 
-				await this.usersStore.submitPersonalizationSurvey(survey);
+const closeDialog = () => {
+	modalBus.emit('close');
+	const isPartOfOnboardingExperiment =
+		posthogStore.getVariant(MORE_ONBOARDING_OPTIONS_EXPERIMENT.name) ===
+		MORE_ONBOARDING_OPTIONS_EXPERIMENT.control;
+	// In case the redirect to homepage for new users didn't happen
+	// we try again after closing the modal
+	if (route.name !== VIEWS.HOMEPAGE && !isPartOfOnboardingExperiment) {
+		void router.replace({ name: VIEWS.HOMEPAGE });
+	}
+};
 
-				this.posthogStore.setMetadata(survey, 'user');
+const onSubmit = async (values: IPersonalizationLatestVersion) => {
+	isSaving.value = true;
 
-				if (Object.keys(values).length === 0) {
-					this.closeDialog();
-				}
-			} catch (e) {
-				this.showError(e, 'Error while submitting results');
-			}
+	try {
+		const completedSurvey: IPersonalizationLatestVersion = {
+			...values,
+			version: SURVEY_VERSION,
+			personalization_survey_submitted_at: new Date().toISOString(),
+			personalization_survey_n8n_version: rootStore.versionCli,
+		};
 
-			let licenseRequestSucceeded = false;
-			try {
-				if (this.registerForEnterpriseTrial && this.canRegisterForEnterpriseTrial) {
-					await this.usageStore.requestEnterpriseLicenseTrial();
-					licenseRequestSucceeded = true;
-					this.$telemetry.track('User registered for self serve trial', {
-						email: this.usersStore.currentUser?.email,
-						instance_id: this.rootStore.instanceId,
-					});
-				}
-			} catch (e) {
-				this.showError(
-					e,
-					this.$locale.baseText('personalizationModal.registerEmailForTrial.error'),
-				);
-			}
+		await externalHooks.run('personalizationModal.onSubmit', completedSurvey);
 
-			this.isSaving = false;
-			this.closeDialog();
+		await usersStore.submitPersonalizationSurvey(completedSurvey);
 
-			if (licenseRequestSucceeded) {
-				await this.alert(
-					this.$locale.baseText('personalizationModal.registerEmailForTrial.success.message'),
-					{
-						title: this.$locale.baseText(
-							'personalizationModal.registerEmailForTrial.success.title',
-						),
-						confirmButtonText: this.$locale.baseText(
-							'personalizationModal.registerEmailForTrial.success.button',
-						),
-					},
-				);
-			}
-		},
-	},
-});
+		posthogStore.setMetadata(completedSurvey, 'user');
+	} catch (e) {
+		showError(e, 'Error while submitting results');
+	} finally {
+		isSaving.value = false;
+		closeDialog();
+	}
+};
 </script>
 
 <template>
@@ -720,24 +615,10 @@ export default defineComponent({
 					:inputs="survey"
 					:column-view="true"
 					:event-bus="formBus"
-					:teleported="teleported"
+					:teleported="true"
 					tag-size="small"
 					@submit="onSubmit"
 				/>
-				<n8n-card v-if="canRegisterForEnterpriseTrial">
-					<n8n-checkbox v-model="registerForEnterpriseTrial">
-						<i18n-t keypath="personalizationModal.registerEmailForTrial">
-							<template #trial>
-								<strong>
-									{{ $locale.baseText('personalizationModal.registerEmailForTrial.enterprise') }}
-								</strong>
-							</template>
-						</i18n-t>
-						<n8n-text size="small" tag="div" color="text-light">
-							{{ $locale.baseText('personalizationModal.registerEmailForTrial.notice') }}
-						</n8n-text>
-					</n8n-checkbox>
-				</n8n-card>
 			</div>
 		</template>
 		<template #footer>
diff --git a/packages/editor-ui/src/components/TagsDropdown.vue b/packages/editor-ui/src/components/TagsDropdown.vue
index cae9c74627dfd..2c25e455fc52a 100644
--- a/packages/editor-ui/src/components/TagsDropdown.vue
+++ b/packages/editor-ui/src/components/TagsDropdown.vue
@@ -182,7 +182,7 @@ onClickOutside(
 	() => {
 		emit('blur');
 	},
-	{ ignore: [`.tags-dropdown-${dropdownId}`, '#tags-manager-modal'] },
+	{ ignore: [`.tags-dropdown-${dropdownId}`, '#tags-manager-modal'], detectIframe: true },
 );
 </script>
 
@@ -199,7 +199,7 @@ onClickOutside(
 			multiple
 			:reserve-keyword="false"
 			loading-text="..."
-			:popper-class="['tags-dropdown', 'tags-dropdown-' + dropdownId]"
+			:popper-class="['tags-dropdown', 'tags-dropdown-' + dropdownId].join(' ')"
 			data-test-id="tags-dropdown"
 			@update:model-value="onTagsUpdated"
 			@visible-change="onVisibleChange"
diff --git a/packages/editor-ui/src/components/__tests__/PersonalizationModal.spec.ts b/packages/editor-ui/src/components/__tests__/PersonalizationModal.spec.ts
deleted file mode 100644
index b688af7cfcb5e..0000000000000
--- a/packages/editor-ui/src/components/__tests__/PersonalizationModal.spec.ts
+++ /dev/null
@@ -1,144 +0,0 @@
-import PersonalizationModal from '@/components/PersonalizationModal.vue';
-import { createTestingPinia } from '@pinia/testing';
-import userEvent from '@testing-library/user-event';
-import { PERSONALIZATION_MODAL_KEY, ROLE, STORES, VIEWS } from '@/constants';
-import { cleanupAppModals, createAppModals, retry } from '@/__tests__/utils';
-import { createComponentRenderer } from '@/__tests__/render';
-import { fireEvent } from '@testing-library/vue';
-import { useUsersStore } from '@/stores/users.store';
-import { useUsageStore } from '@/stores/usage.store';
-
-const pinia = createTestingPinia({
-	initialState: {
-		[STORES.UI]: {
-			modalsById: {
-				[PERSONALIZATION_MODAL_KEY]: { open: true },
-			},
-		},
-		[STORES.SETTINGS]: {
-			settings: {
-				templates: {
-					host: '',
-				},
-			},
-		},
-		[STORES.USERS]: {
-			usersById: {
-				123: {
-					email: 'john@doe.com',
-					firstName: 'John',
-					lastName: 'Doe',
-					isDefaultUser: false,
-					isPendingUser: false,
-					role: ROLE.Owner,
-					mfaEnabled: false,
-				},
-			},
-			currentUserId: '123',
-		},
-	},
-});
-
-const renderComponent = createComponentRenderer(PersonalizationModal, {
-	props: {
-		teleported: false,
-		appendToBody: false,
-	},
-	pinia,
-	global: {
-		mocks: {
-			$route: {
-				name: VIEWS.HOMEPAGE,
-			},
-		},
-	},
-});
-
-describe('PersonalizationModal.vue', () => {
-	beforeEach(() => {
-		createAppModals();
-	});
-
-	afterEach(() => {
-		cleanupAppModals();
-	});
-
-	it('should render correctly', async () => {
-		const { getByTestId } = renderComponent();
-
-		await retry(() => expect(getByTestId('personalization-form')).toBeInTheDocument());
-
-		const modalContent = getByTestId('personalization-form');
-		expect(modalContent.querySelectorAll('.n8n-select').length).toEqual(5);
-	});
-
-	it('should display new option when role is "Devops", "Engineering", "IT", or "Sales and marketing"', async () => {
-		const { getByTestId } = renderComponent();
-
-		await retry(() => expect(getByTestId('personalization-form')).toBeInTheDocument());
-
-		for (const index of [3, 4, 5, 6]) {
-			const modalContent = getByTestId('personalization-form');
-			const expectFn = expect; // So we don't break @typescript-eslint/no-loop-func
-			const select = modalContent.querySelectorAll('.n8n-select')[1];
-
-			await fireEvent.click(select);
-
-			const item = select.querySelectorAll('.el-select-dropdown__item')[index];
-
-			await fireEvent.click(item);
-
-			await retry(() => {
-				expectFn(modalContent.querySelectorAll('.n8n-select').length).toEqual(6);
-				expectFn(modalContent.querySelector('[name^="automationGoal"]')).toBeInTheDocument();
-			});
-		}
-	});
-
-	it('should display self serve trial option when company size is larger than 500', async () => {
-		const { getByTestId } = renderComponent();
-
-		await retry(() => expect(getByTestId('personalization-form')).toBeInTheDocument());
-
-		const modalContent = getByTestId('personalization-form');
-
-		const select = modalContent.querySelectorAll('.n8n-select')[3];
-		await fireEvent.click(select);
-
-		const item = select.querySelectorAll('.el-select-dropdown__item')[3];
-		await fireEvent.click(item);
-
-		await retry(() => {
-			expect(modalContent.querySelector('.card')).not.toBeNull();
-		});
-	});
-
-	it('should display send telemetry when requesting enterprise trial', async () => {
-		const usersStore = useUsersStore(pinia);
-		vi.spyOn(usersStore, 'submitPersonalizationSurvey').mockResolvedValue();
-
-		const usageStore = useUsageStore(pinia);
-		const spyLicenseTrial = vi.spyOn(usageStore, 'requestEnterpriseLicenseTrial');
-
-		const { getByTestId, getByRole } = renderComponent();
-
-		await retry(() => expect(getByTestId('personalization-form')).toBeInTheDocument());
-
-		const modalContent = getByTestId('personalization-form');
-
-		const select = modalContent.querySelectorAll('.n8n-select')[3];
-		await fireEvent.click(select);
-
-		const item = select.querySelectorAll('.el-select-dropdown__item')[3];
-		await fireEvent.click(item);
-
-		const agreeCheckbox = modalContent.querySelector('.n8n-checkbox');
-		assert(agreeCheckbox);
-		await fireEvent.click(agreeCheckbox);
-
-		const submitButton = getByRole('button');
-		await userEvent.click(submitButton);
-
-		await retry(() => expect(spyLicenseTrial).toHaveBeenCalled());
-	});
-});
diff --git a/packages/editor-ui/src/components/executions/workflow/WorkflowExecutionAnnotationSidebar.vue b/packages/editor-ui/src/components/executions/workflow/WorkflowExecutionAnnotationPanel.vue
similarity index 53%
rename from packages/editor-ui/src/components/executions/workflow/WorkflowExecutionAnnotationSidebar.vue
rename to packages/editor-ui/src/components/executions/workflow/WorkflowExecutionAnnotationPanel.vue
index 94ff1f406bc0b..1ae4ef0e5c4df 100644
--- a/packages/editor-ui/src/components/executions/workflow/WorkflowExecutionAnnotationSidebar.vue
+++ b/packages/editor-ui/src/components/executions/workflow/WorkflowExecutionAnnotationPanel.vue
@@ -1,16 +1,32 @@
-<script lang="ts">
+<script setup lang="ts">
+import { ref, computed } from 'vue';
 import type { AnnotationVote, ExecutionSummary } from 'n8n-workflow';
-import { defineComponent } from 'vue';
-import type { PropType } from 'vue';
-import { mapStores } from 'pinia';
 import { useExecutionsStore } from '@/stores/executions.store';
-import { useWorkflowsStore } from '@/stores/workflows.store';
 import AnnotationTagsDropdown from '@/components/AnnotationTagsDropdown.vue';
 import { createEventBus } from 'n8n-design-system';
 import VoteButtons from '@/components/executions/workflow/VoteButtons.vue';
 import { useToast } from '@/composables/useToast';
 
-const hasChanged = (prev: string[], curr: string[]) => {
+const executionsStore = useExecutionsStore();
+
+const { showError } = useToast();
+
+const tagsEventBus = createEventBus();
+const isTagsEditEnabled = ref(false);
+const appliedTagIds = ref<string[]>([]);
+const tagsSaving = ref(false);
+
+const activeExecution = computed(() => {
+	return executionsStore.activeExecution as ExecutionSummary & {
+		customData?: Record<string, string>;
+	};
+});
+
+const vote = computed(() => activeExecution.value?.annotation?.vote || null);
+const tagIds = computed(() => activeExecution.value?.annotation?.tags.map((tag) => tag.id) ?? []);
+const tags = computed(() => activeExecution.value?.annotation?.tags);
+
+const tagsHasChanged = (prev: string[], curr: string[]) => {
 	if (prev.length !== curr.length) {
 		return true;
 	}
@@ -19,129 +35,79 @@ const hasChanged = (prev: string[], curr: string[]) => {
 	return curr.reduce((acc, val) => acc || !set.has(val), false);
 };
 
-export default defineComponent({
-	name: 'WorkflowExecutionAnnotationSidebar',
-	components: {
-		VoteButtons,
-		AnnotationTagsDropdown,
-	},
-	props: {
-		execution: {
-			type: Object as PropType<ExecutionSummary>,
-			default: null,
-		},
-		loading: {
-			type: Boolean,
-			default: true,
-		},
-	},
-
-	computed: {
-		...mapStores(useExecutionsStore, useWorkflowsStore),
-		vote() {
-			return this.activeExecution?.annotation?.vote || null;
-		},
-		activeExecution() {
-			// FIXME: this is a temporary workaround to make TS happy. activeExecution may contain customData, but it is type-casted to ExecutionSummary after fetching from the backend
-			return this.executionsStore.activeExecution as ExecutionSummary & {
-				customData?: Record<string, string>;
-			};
-		},
-		tagIds() {
-			return this.activeExecution?.annotation?.tags.map((tag) => tag.id) ?? [];
-		},
-		tags() {
-			return this.activeExecution?.annotation?.tags;
-		},
-	},
-	setup() {
-		return {
-			...useToast(),
-		};
-	},
-	data() {
-		return {
-			tagsEventBus: createEventBus(),
-			isTagsEditEnabled: false,
-			appliedTagIds: [] as string[],
-			tagsSaving: false,
-		};
-	},
-	methods: {
-		async onVoteClick(vote: AnnotationVote) {
-			if (!this.activeExecution) {
-				return;
-			}
-
-			// If user clicked on the same vote, remove it
-			// so that vote buttons act as toggle buttons
-			const voteToSet = vote === this.vote ? null : vote;
-
-			try {
-				await this.executionsStore.annotateExecution(this.activeExecution.id, { vote: voteToSet });
-			} catch (e) {
-				this.showError(e, this.$locale.baseText('executionAnnotationView.vote.error'));
-			}
-		},
-		onTagsEditEnable() {
-			this.appliedTagIds = this.tagIds;
-			this.isTagsEditEnabled = true;
-
-			setTimeout(() => {
-				this.tagsEventBus.emit('focus');
-			}, 0);
-		},
-		async onTagsBlur() {
-			if (!this.activeExecution) {
-				return;
-			}
-
-			const current = (this.tagIds ?? []) as string[];
-			const tags = this.appliedTagIds;
-
-			if (!hasChanged(current, tags)) {
-				this.isTagsEditEnabled = false;
-				return;
-			}
-
-			if (this.tagsSaving) {
-				return;
-			}
-
-			this.tagsSaving = true;
-
-			try {
-				await this.executionsStore.annotateExecution(this.activeExecution.id, { tags });
-			} catch (e) {
-				this.showError(e, this.$locale.baseText('executionAnnotationView.tag.error'));
-			}
-
-			this.tagsSaving = false;
-			this.isTagsEditEnabled = false;
-		},
-		onTagsEditEsc() {
-			this.isTagsEditEnabled = false;
-		},
-	},
-});
+const onVoteClick = async (voteValue: AnnotationVote) => {
+	if (!activeExecution.value) {
+		return;
+	}
+
+	const voteToSet = voteValue === vote.value ? null : voteValue;
+
+	try {
+		await executionsStore.annotateExecution(activeExecution.value.id, { vote: voteToSet });
+	} catch (e) {
+		showError(e, 'executionAnnotationView.vote.error');
+	}
+};
+
+const onTagsEditEnable = () => {
+	appliedTagIds.value = tagIds.value;
+	isTagsEditEnabled.value = true;
+
+	setTimeout(() => {
+		tagsEventBus.emit('focus');
+	}, 0);
+};
+
+const onTagsBlur = async () => {
+	if (!activeExecution.value) {
+		return;
+	}
+
+	const currentTagIds = tagIds.value ?? [];
+	const newTagIds = appliedTagIds.value;
+
+	if (!tagsHasChanged(currentTagIds, newTagIds)) {
+		isTagsEditEnabled.value = false;
+		return;
+	}
+
+	if (tagsSaving.value) {
+		return;
+	}
+
+	tagsSaving.value = true;
+
+	try {
+		await executionsStore.annotateExecution(activeExecution.value.id, { tags: newTagIds });
+	} catch (e) {
+		showError(e, 'executionAnnotationView.tag.error');
+	}
+
+	tagsSaving.value = false;
+	isTagsEditEnabled.value = false;
+};
+
+const onTagsEditEsc = () => {
+	isTagsEditEnabled.value = false;
+};
 </script>
 
 <template>
 	<div
 		ref="container"
-		:class="['execution-annotation-sidebar', $style.container]"
-		data-test-id="execution-annotation-sidebar"
+		:class="['execution-annotation-panel', $style.container]"
+		data-test-id="execution-annotation-panel"
 	>
 		<div :class="$style.section">
 			<div :class="$style.vote">
 				<div>{{ $locale.baseText('generic.rating') }}</div>
 				<VoteButtons :vote="vote" @vote-click="onVoteClick" />
 			</div>
-			<span class="tags" data-test-id="annotation-tags-container">
+			<span :class="$style.tags" data-test-id="annotation-tags-container">
 				<AnnotationTagsDropdown
 					v-if="isTagsEditEnabled"
-					v-model="appliedTagIds"
 					ref="dropdown"
+					v-model="appliedTagIds"
 					:create-enabled="true"
 					:event-bus="tagsEventBus"
 					:placeholder="$locale.baseText('executionAnnotationView.chooseOrCreateATag')"
@@ -152,7 +118,7 @@ export default defineComponent({
 				/>
 				<div v-else-if="tagIds.length === 0">
 					<span
-						class="add-tag add-tag-standalone clickable"
+						:class="[$style.addTag, $style.addTagStandalone, 'clickable']"
 						data-test-id="new-tag-link"
 						@click="onTagsEditEnable"
 					>
@@ -162,7 +128,10 @@ export default defineComponent({
 
 				<span
 					v-else
-					class="tags-container"
+					:class="[
+						'tags-container', // FIXME: There are some global styles for tags relying on this classname
+						$style.tagsContainer,
+					]"
 					data-test-id="execution-annotation-tags"
 					@click="onTagsEditEnable"
 				>
@@ -171,9 +140,9 @@ export default defineComponent({
 							{{ tag.name }}
 						</el-tag>
 					</span>
-					<span class="add-tag-wrapper">
+					<span :class="$style.addTagWrapper">
 						<n8n-button
-							class="add-tag"
+							:class="$style.addTag"
 							:label="`+ ` + $locale.baseText('executionAnnotationView.addTag')"
 							type="secondary"
 							size="mini"
@@ -208,7 +177,7 @@ export default defineComponent({
 					</n8n-text>
 				</div>
 			</div>
-			<div v-else :class="$style.noResultsContainer" data-test-id="execution-list-empty">
+			<div v-else :class="$style.noResultsContainer" data-test-id="execution-annotation-data-empty">
 				<n8n-text color="text-base" size="small" align="center">
 					<span v-html="$locale.baseText('executionAnnotationView.data.notFound')" />
 				</n8n-text>
@@ -219,23 +188,29 @@ export default defineComponent({
 
 <style module lang="scss">
 .container {
-	flex: 250px 0 0;
-	background-color: var(--color-background-xlight);
-	border-left: var(--border-base);
 	z-index: 1;
+	position: absolute;
+	bottom: 0;
+	right: var(--spacing-xl);
+	transform: translate(0, 100%);
+	max-height: calc(100vh - 250px);
+	width: 250px;
+
 	display: flex;
 	flex-direction: column;
 	overflow: auto;
+
+	background-color: var(--color-background-xlight);
+	border: var(--border-base);
+	border-radius: var(--border-radius-base);
 }
 
 .section {
-	padding: var(--spacing-l);
+	padding: var(--spacing-s);
 	display: flex;
 	flex-direction: column;
 
 	&:not(:last-child) {
-		display: flex;
-		padding-bottom: var(--spacing-l);
 		border-bottom: var(--border-base);
 	}
 }
@@ -296,57 +271,19 @@ export default defineComponent({
 	}
 }
 
-.executionList {
-	flex: 1;
-	overflow: auto;
-	margin-bottom: var(--spacing-m);
-	background-color: var(--color-background-xlight) !important;
-
-	// Scrolling fader
-	&::before {
-		position: absolute;
-		display: block;
-		width: 270px;
-		height: 6px;
-		background: linear-gradient(to bottom, rgba(251, 251, 251, 1) 0%, rgba(251, 251, 251, 0) 100%);
-		z-index: 999;
-	}
-
-	// Lower first execution card so fader is not visible when not scrolled
-	& > div:first-child {
-		margin-top: 3px;
-	}
-}
-
-.infoAccordion {
-	position: absolute;
-	bottom: 0;
-	margin-left: calc(-1 * var(--spacing-l));
-	border-top: var(--border-base);
-
-	& > div {
-		width: 309px;
-		background-color: var(--color-background-light);
-		margin-top: 0 !important;
-	}
-}
-
 .noResultsContainer {
 	width: 100%;
 	margin-top: var(--spacing-s);
-	//text-align: center;
 }
-</style>
 
-<style lang="scss" scoped>
-.execution-annotation-sidebar {
+.execution-annotation-panel {
 	:deep(.el-skeleton__item) {
 		height: 60px;
 		border-radius: 0;
 	}
 }
 
-.tags-container {
+.tagsContainer {
 	display: inline-flex;
 	flex-wrap: wrap;
 	align-items: center;
@@ -358,10 +295,10 @@ export default defineComponent({
 	}
 }
 
-.add-tag {
-	font-size: 12px;
+.addTag {
+	font-size: var(--font-size-2xs);
 	color: $custom-font-very-light;
-	font-weight: 600;
+	font-weight: var(--font-weight-bold);
 	white-space: nowrap;
 	&:hover {
 		color: $color-primary;
@@ -369,11 +306,11 @@ export default defineComponent({
 	}
 }
 
-.add-tag-standalone {
-	padding: 20px 0; // to be more clickable
+.addTagStandalone {
+	padding: var(--spacing-m) 0; // to be more clickable
 }
 
-.add-tag-wrapper {
+.addTagWrapper {
 	margin-left: calc(var(--spacing-2xs) * -1); // Cancel out right margin of last tag
 }
 </style>
diff --git a/packages/editor-ui/src/components/executions/workflow/WorkflowExecutionsList.vue b/packages/editor-ui/src/components/executions/workflow/WorkflowExecutionsList.vue
index 2944b0daddb42..fda8e084876bb 100644
--- a/packages/editor-ui/src/components/executions/workflow/WorkflowExecutionsList.vue
+++ b/packages/editor-ui/src/components/executions/workflow/WorkflowExecutionsList.vue
@@ -2,18 +2,11 @@
 import { computed, watch } from 'vue';
 import { onBeforeRouteLeave, useRouter } from 'vue-router';
 import WorkflowExecutionsSidebar from '@/components/executions/workflow/WorkflowExecutionsSidebar.vue';
-import {
-	EnterpriseEditionFeature,
-	EXECUTION_ANNOTATION_EXPERIMENT,
-	MAIN_HEADER_TABS,
-	VIEWS,
-} from '@/constants';
+import { MAIN_HEADER_TABS, VIEWS } from '@/constants';
 import type { ExecutionFilterType, IWorkflowDb } from '@/Interface';
 import type { ExecutionSummary } from 'n8n-workflow';
 import { getNodeViewTab } from '@/utils/canvasUtils';
 import { useWorkflowHelpers } from '@/composables/useWorkflowHelpers';
-import { usePostHog } from '@/stores/posthog.store';
-import { useSettingsStore } from '@/stores/settings.store';
 
 const props = withDefaults(
 	defineProps<{
@@ -43,18 +36,6 @@ const emit = defineEmits<{
 const workflowHelpers = useWorkflowHelpers({ router: useRouter() });
 const router = useRouter();
 
-const posthogStore = usePostHog();
-const settingsStore = useSettingsStore();
-
-const isAdvancedExecutionFilterEnabled = computed(
-	() => settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.AdvancedExecutionFilters],
-);
-const isAnnotationEnabled = computed(
-	() =>
-		isAdvancedExecutionFilterEnabled.value &&
-		posthogStore.isFeatureEnabled(EXECUTION_ANNOTATION_EXPERIMENT),
-);
-
 const temporaryExecution = computed<ExecutionSummary | undefined>(() =>
 	props.executions.find((execution) => execution.id === props.execution?.id)
 		? undefined
@@ -135,10 +116,6 @@ onBeforeRouteLeave(async (to, _, next) => {
 				@stop-execution="onStopExecution"
 			/>
 		</div>
-		<WorkflowExecutionAnnotationSidebar
-			v-if="isAnnotationEnabled && execution"
-			:execution="execution"
-		/>
 	</div>
 </template>
 
diff --git a/packages/editor-ui/src/components/executions/workflow/WorkflowExecutionsPreview.vue b/packages/editor-ui/src/components/executions/workflow/WorkflowExecutionsPreview.vue
index 677046f4bdc79..8fe995853061f 100644
--- a/packages/editor-ui/src/components/executions/workflow/WorkflowExecutionsPreview.vue
+++ b/packages/editor-ui/src/components/executions/workflow/WorkflowExecutionsPreview.vue
@@ -5,13 +5,20 @@ import { ElDropdown } from 'element-plus';
 import { useExecutionDebugging } from '@/composables/useExecutionDebugging';
 import { useMessage } from '@/composables/useMessage';
 import WorkflowPreview from '@/components/WorkflowPreview.vue';
-import { MODAL_CONFIRM, VIEWS } from '@/constants';
+import {
+	EnterpriseEditionFeature,
+	EXECUTION_ANNOTATION_EXPERIMENT,
+	MODAL_CONFIRM,
+	VIEWS,
+} from '@/constants';
 import type { ExecutionSummary } from 'n8n-workflow';
 import type { IExecutionUIData } from '@/composables/useExecutionHelpers';
 import { useExecutionHelpers } from '@/composables/useExecutionHelpers';
 import { useWorkflowsStore } from '@/stores/workflows.store';
 import { useI18n } from '@/composables/useI18n';
 import { getResourcePermissions } from '@/permissions';
+import { usePostHog } from '@/stores/posthog.store';
+import { useSettingsStore } from '@/stores/settings.store';
 
 type RetryDropdownRef = InstanceType<typeof ElDropdown>;
 
@@ -32,6 +39,8 @@ const executionHelpers = useExecutionHelpers();
 const message = useMessage();
 const executionDebugging = useExecutionDebugging();
 const workflowsStore = useWorkflowsStore();
+const posthogStore = usePostHog();
+const settingsStore = useSettingsStore();
 
 const retryDropdownRef = ref<RetryDropdownRef | null>(null);
 const workflowId = computed(() => route.params.name as string);
@@ -57,6 +66,12 @@ const isRetriable = computed(
 	() => !!props.execution && executionHelpers.isExecutionRetriable(props.execution),
 );
 
+const isAnnotationEnabled = computed(
+	() =>
+		settingsStore.isEnterpriseFeatureEnabled[EnterpriseEditionFeature.AdvancedExecutionFilters] &&
+		posthogStore.isFeatureEnabled(EXECUTION_ANNOTATION_EXPERIMENT),
+);
+
 async function onDeleteExecution(): Promise<void> {
 	const deleteConfirmed = await message.confirm(
 		locale.baseText('executionDetails.confirmMessage.message'),
@@ -115,6 +130,7 @@ function onRetryButtonBlur(event: FocusEvent) {
 			:class="$style.executionDetails"
 			:data-test-id="`execution-preview-details-${executionId}`"
 		>
+			<WorkflowExecutionAnnotationPanel v-if="isAnnotationEnabled && execution" />
 			<div>
 				<N8nText size="large" color="text-base" :bold="true" data-test-id="execution-time">{{
 					executionUIDetails?.startTime
diff --git a/packages/editor-ui/src/router.ts b/packages/editor-ui/src/router.ts
index 6c6a68b22d7a3..96a189071bd8e 100644
--- a/packages/editor-ui/src/router.ts
+++ b/packages/editor-ui/src/router.ts
@@ -328,6 +328,7 @@ export const routes: RouteRecordRaw[] = [
 			default: NodeView,
 		},
 		meta: {
+			nodeView: true,
 			middleware: ['authenticated'],
 			middlewareOptions: {
 				authenticated: {
diff --git a/packages/editor-ui/src/stores/workflows.store.ts b/packages/editor-ui/src/stores/workflows.store.ts
index c6ca53ac2338b..9b3470d27b224 100644
--- a/packages/editor-ui/src/stores/workflows.store.ts
+++ b/packages/editor-ui/src/stores/workflows.store.ts
@@ -428,9 +428,9 @@ export const useWorkflowsStore = defineStore(STORES.WORKFLOWS, () => {
 
 	async function fetchWorkflow(id: string): Promise<IWorkflowDb> {
 		const rootStore = useRootStore();
-		const workflow = await workflowsApi.getWorkflow(rootStore.restApiContext, id);
-		addWorkflow(workflow);
-		return workflow;
+		const workflowData = await workflowsApi.getWorkflow(rootStore.restApiContext, id);
+		addWorkflow(workflowData);
+		return workflowData;
 	}
 
 	async function getNewWorkflowData(name?: string, projectId?: string): Promise<INewWorkflowData> {
diff --git a/packages/editor-ui/src/views/NodeView.v2.vue b/packages/editor-ui/src/views/NodeView.v2.vue
index 4f5f16caa58fe..f69d9b7eb9d84 100644
--- a/packages/editor-ui/src/views/NodeView.v2.vue
+++ b/packages/editor-ui/src/views/NodeView.v2.vue
@@ -5,13 +5,13 @@ import {
 	nextTick,
 	onActivated,
 	onBeforeMount,
-	onBeforeUnmount,
 	onDeactivated,
 	onMounted,
 	ref,
 	useCssModule,
 	watch,
 	h,
+	onBeforeUnmount,
 } from 'vue';
 import { useRoute, useRouter } from 'vue-router';
 import WorkflowCanvas from '@/components/canvas/WorkflowCanvas.vue';
@@ -53,7 +53,9 @@ import {
 	MAIN_HEADER_TABS,
 	MANUAL_CHAT_TRIGGER_NODE_TYPE,
 	MODAL_CONFIRM,
+	NEW_WORKFLOW_ID,
 	NODE_CREATOR_OPEN_SOURCES,
+	PLACEHOLDER_EMPTY_WORKFLOW_ID,
 	START_NODE_TYPE,
 	STICKY_NODE_TYPE,
 	VALID_WORKFLOW_IMPORT_URL_REGEX,
@@ -152,7 +154,7 @@ const canvasEventBus = createEventBus<CanvasEventBusEvents>();
 const { addBeforeUnloadEventBindings, removeBeforeUnloadEventBindings } = useBeforeUnload({
 	route,
 });
-const { registerCustomAction } = useGlobalLinkActions();
+const { registerCustomAction, unregisterCustomAction } = useGlobalLinkActions();
 const { runWorkflow, stopCurrentExecution, stopWaitingForWebhook } = useRunWorkflow({ router });
 const {
 	updateNodePosition,
@@ -201,12 +203,18 @@ const isExecutionPreview = ref(false);
 const canOpenNDV = ref(true);
 const hideNodeIssues = ref(false);
 
-const workflowId = computed<string>(() => route.params.name as string);
-const workflow = computed(() => workflowsStore.workflowsById[workflowId.value]);
+const initializedWorkflowId = ref<string | undefined>();
+const workflowId = computed(() => {
+	const workflowIdParam = route.params.name as string;
+	return [PLACEHOLDER_EMPTY_WORKFLOW_ID, NEW_WORKFLOW_ID].includes(workflowIdParam)
+		? undefined
+		: workflowIdParam;
+});
 
-const isNewWorkflowRoute = computed(() => route.name === VIEWS.NEW_WORKFLOW);
+const isNewWorkflowRoute = computed(() => route.name === VIEWS.NEW_WORKFLOW || !workflowId.value);
+const isWorkflowRoute = computed(() => !!route?.meta?.nodeView);
 const isDemoRoute = computed(() => route.name === VIEWS.DEMO);
-const isReadOnlyRoute = computed(() => route?.meta?.readOnlyCanvas === true);
+const isReadOnlyRoute = computed(() => !!route?.meta?.readOnlyCanvas);
 const isReadOnlyEnvironment = computed(() => {
 	return sourceControlStore.preferences.branchReadOnly;
 });
@@ -287,6 +295,10 @@ async function initializeRoute() {
 		return;
 	}
 
+	const isAlreadyInitialized =
+		initializedWorkflowId.value &&
+		[NEW_WORKFLOW_ID, workflowId.value].includes(initializedWorkflowId.value);
+
 	// This function is called on route change as well, so we need to do the following:
 	// - if the redirect is blank, then do nothing
 	// - if the route is the template import view, then open the template
@@ -296,11 +308,11 @@ async function initializeRoute() {
 	} else if (route.name === VIEWS.TEMPLATE_IMPORT) {
 		const templateId = route.params.id;
 		await openWorkflowTemplate(templateId.toString());
-	} else {
+	} else if (isWorkflowRoute.value && !isAlreadyInitialized) {
 		historyStore.reset();
 
 		// If there is no workflow id, treat it as a new workflow
-		if (!workflowId.value || isNewWorkflowRoute.value) {
+		if (isNewWorkflowRoute.value || !workflowId.value) {
 			if (route.meta?.nodeView === true) {
 				await initializeWorkspaceForNewWorkflow();
 			}
@@ -308,14 +320,14 @@ async function initializeRoute() {
 		}
 
 		await initializeWorkspaceForExistingWorkflow(workflowId.value);
-	}
 
-	nodeHelpers.updateNodesInputIssues();
-	nodeHelpers.updateNodesCredentialsIssues();
-	nodeHelpers.updateNodesParameterIssues();
+		nodeHelpers.updateNodesInputIssues();
+		nodeHelpers.updateNodesCredentialsIssues();
+		nodeHelpers.updateNodesParameterIssues();
 
-	await loadCredentials();
-	await initializeDebugMode();
+		await loadCredentials();
+		await initializeDebugMode();
+	}
 }
 
 async function initializeWorkspaceForNewWorkflow() {
@@ -325,11 +337,10 @@ async function initializeWorkspaceForNewWorkflow() {
 	workflowsStore.makeNewWorkflowShareable();
 
 	uiStore.nodeViewInitialized = true;
+	initializedWorkflowId.value = NEW_WORKFLOW_ID;
 }
 
 async function initializeWorkspaceForExistingWorkflow(id: string) {
-	resetWorkspace();
-
 	try {
 		const workflowData = await workflowsStore.fetchWorkflow(id);
 
@@ -339,7 +350,9 @@ async function initializeWorkspaceForExistingWorkflow(id: string) {
 			trackOpenWorkflowFromOnboardingTemplate();
 		}
 
-		await projectsStore.setProjectNavActiveIdByWorkflowHomeProject(workflow.value.homeProject);
+		await projectsStore.setProjectNavActiveIdByWorkflowHomeProject(
+			editableWorkflow.value.homeProject,
+		);
 
 		collaborationStore.notifyWorkflowOpened(id);
 	} catch (error) {
@@ -350,6 +363,7 @@ async function initializeWorkspaceForExistingWorkflow(id: string) {
 		});
 	} finally {
 		uiStore.nodeViewInitialized = true;
+		initializedWorkflowId.value = workflowId.value;
 	}
 }
 
@@ -359,7 +373,7 @@ async function initializeWorkspaceForExistingWorkflow(id: string) {
 
 async function openWorkflow(data: IWorkflowDb) {
 	resetWorkspace();
-	titleSet(workflow.value.name, 'IDLE');
+	titleSet(editableWorkflow.value.name, 'IDLE');
 
 	await initializeWorkspace(data);
 
@@ -382,7 +396,7 @@ async function openWorkflow(data: IWorkflowDb) {
 
 function trackOpenWorkflowFromOnboardingTemplate() {
 	telemetry.track(
-		`User opened workflow from onboarding template with ID ${workflow.value.meta?.onboardingId}`,
+		`User opened workflow from onboarding template with ID ${editableWorkflow.value.meta?.onboardingId}`,
 		{
 			workflow_id: workflowId.value,
 		},
@@ -716,8 +730,8 @@ function onClickNodeAdd(source: string, sourceHandle: string) {
 async function loadCredentials() {
 	let options: { workflowId: string } | { projectId: string };
 
-	if (workflow.value) {
-		options = { workflowId: workflow.value.id };
+	if (editableWorkflow.value) {
+		options = { workflowId: editableWorkflow.value.id };
 	} else {
 		const queryParam =
 			typeof route.query?.projectId === 'string' ? route.query?.projectId : undefined;
@@ -917,7 +931,9 @@ function onClickConnectionAdd(connection: Connection) {
  */
 
 const workflowPermissions = computed(() => {
-	return getResourcePermissions(workflowsStore.getWorkflowById(workflowId.value)?.scopes).workflow;
+	return workflowId.value
+		? getResourcePermissions(workflowsStore.getWorkflowById(workflowId.value)?.scopes).workflow
+		: {};
 });
 
 const projectPermissions = computed(() => {
@@ -1200,7 +1216,7 @@ async function onSourceControlPull() {
 			loadCredentials(),
 		]);
 
-		if (workflowId.value !== null && !uiStore.stateIsDirty) {
+		if (workflowId.value && !uiStore.stateIsDirty) {
 			const workflowData = await workflowsStore.fetchWorkflow(workflowId.value);
 			if (workflowData) {
 				titleSet(workflowData.name, 'IDLE');
@@ -1306,6 +1322,10 @@ async function onPostMessageReceived(messageEvent: MessageEvent) {
  */
 
 function checkIfEditingIsAllowed(): boolean {
+	if (!initializedWorkflowId.value) {
+		return true;
+	}
+
 	if (readOnlyNotification.value?.visible) {
 		return false;
 	}
@@ -1438,6 +1458,12 @@ function registerCustomActions() {
 	});
 }
 
+function unregisterCustomActions() {
+	unregisterCustomAction('openNodeDetail');
+	unregisterCustomAction('openSelectiveNodeCreator');
+	unregisterCustomAction('showNodeCreator');
+}
+
 /**
  * Routing
  */
@@ -1445,10 +1471,6 @@ function registerCustomActions() {
 watch(
 	() => route.name,
 	async () => {
-		if (!checkIfEditingIsAllowed()) {
-			return;
-		}
-
 		await initializeRoute();
 	},
 );
@@ -1464,8 +1486,9 @@ onBeforeMount(() => {
 	}
 });
 
-onMounted(async () => {
+onMounted(() => {
 	canvasStore.startLoading();
+
 	titleReset();
 	resetWorkspace();
 
@@ -1479,6 +1502,8 @@ onMounted(async () => {
 			.finally(() => {
 				isLoading.value = false;
 				canvasStore.stopLoading();
+
+				void externalHooks.run('nodeView.mount').catch(() => {});
 			});
 
 		void usersStore.showPersonalizationSurvey();
@@ -1486,34 +1511,31 @@ onMounted(async () => {
 		checkIfRouteIsAllowed();
 	});
 
-	addUndoRedoEventBindings();
-	addPostMessageEventBindings();
 	addSourceControlEventBindings();
+	addPostMessageEventBindings();
+	addWorkflowSavedEventBindings();
+	addBeforeUnloadEventBindings();
 	addImportEventBindings();
 	addExecutionOpenedEventBindings();
-	addWorkflowSavedEventBindings();
-
 	registerCustomActions();
+});
 
-	// @TODO: This currently breaks since front-end hooks are still not updated to work with pinia store
-	void externalHooks.run('nodeView.mount').catch(() => {});
+onActivated(async () => {
+	addUndoRedoEventBindings();
 });
 
-onActivated(() => {
-	addBeforeUnloadEventBindings();
+onDeactivated(() => {
+	removeUndoRedoEventBindings();
 });
 
 onBeforeUnmount(() => {
-	removeUndoRedoEventBindings();
-	removePostMessageEventBindings();
 	removeSourceControlEventBindings();
-	removeImportEventBindings();
-	removeExecutionOpenedEventBindings();
+	removePostMessageEventBindings();
 	removeWorkflowSavedEventBindings();
-});
-
-onDeactivated(() => {
 	removeBeforeUnloadEventBindings();
+	removeImportEventBindings();
+	removeExecutionOpenedEventBindings();
+	unregisterCustomActions();
 	collaborationStore.terminate();
 });
 </script>
diff --git a/packages/editor-ui/src/views/WorkflowExecutionsView.vue b/packages/editor-ui/src/views/WorkflowExecutionsView.vue
index a1ad39c4f3676..a9abf67ee4b59 100644
--- a/packages/editor-ui/src/views/WorkflowExecutionsView.vue
+++ b/packages/editor-ui/src/views/WorkflowExecutionsView.vue
@@ -8,7 +8,7 @@ import { useWorkflowsStore } from '@/stores/workflows.store';
 import { useNodeTypesStore } from '@/stores/nodeTypes.store';
 import { NO_NETWORK_ERROR_CODE } from '@/utils/apiUtils';
 import { useToast } from '@/composables/useToast';
-import { PLACEHOLDER_EMPTY_WORKFLOW_ID, VIEWS } from '@/constants';
+import { NEW_WORKFLOW_ID, PLACEHOLDER_EMPTY_WORKFLOW_ID, VIEWS } from '@/constants';
 import { useRoute, useRouter } from 'vue-router';
 import type { ExecutionSummary } from 'n8n-workflow';
 import { useDebounce } from '@/composables/useDebounce';
@@ -34,15 +34,22 @@ const loadingMore = ref(false);
 const workflow = ref<IWorkflowDb | undefined>();
 
 const workflowId = computed(() => {
-	return (route.params.name as string) || workflowsStore.workflowId;
+	const workflowIdParam = route.params.name as string;
+	return [PLACEHOLDER_EMPTY_WORKFLOW_ID, NEW_WORKFLOW_ID].includes(workflowIdParam)
+		? undefined
+		: workflowIdParam;
 });
 
 const executionId = computed(() => route.params.executionId as string);
 
-const executions = computed(() => [
-	...(executionsStore.currentExecutionsByWorkflowId[workflowId.value] ?? []),
-	...(executionsStore.executionsByWorkflowId[workflowId.value] ?? []),
-]);
+const executions = computed(() =>
+	workflowId.value
+		? [
+				...(executionsStore.currentExecutionsByWorkflowId[workflowId.value] ?? []),
+				...(executionsStore.executionsByWorkflowId[workflowId.value] ?? []),
+			]
+		: [],
+);
 
 const execution = computed(() => {
 	return executions.value.find((e) => e.id === executionId.value) ?? currentExecution.value;
@@ -65,13 +72,12 @@ watch(
 );
 
 onMounted(async () => {
-	await nodeTypesStore.loadNodeTypesIfNotLoaded();
-	await Promise.all([
-		nodeTypesStore.loadNodeTypesIfNotLoaded(),
-		fetchWorkflow(),
-		executionsStore.initialize(workflowId.value),
-	]);
-	await fetchExecution();
+	await Promise.all([nodeTypesStore.loadNodeTypesIfNotLoaded(), fetchWorkflow()]);
+
+	if (workflowId.value) {
+		await Promise.all([executionsStore.initialize(workflowId.value), fetchExecution()]);
+	}
+
 	await initializeRoute();
 	document.addEventListener('visibilitychange', onDocumentVisibilityChange);
 });
@@ -116,18 +122,22 @@ async function initializeRoute() {
 }
 
 async function fetchWorkflow() {
-	if (workflowsStore.workflow.id === PLACEHOLDER_EMPTY_WORKFLOW_ID) {
-		try {
-			await workflowsStore.fetchActiveWorkflows();
-			const data = await workflowsStore.fetchWorkflow(workflowId.value);
-			workflowHelpers.initState(data);
-			await nodeHelpers.addNodes(data.nodes, data.connections);
-			workflow.value = workflowsStore.workflow;
-		} catch (error) {
-			toast.showError(error, i18n.baseText('nodeView.showError.openWorkflow.title'));
+	if (workflowId.value) {
+		// Check if we are loading the Executions tab directly, without having loaded the workflow
+		if (workflowsStore.workflow.id === PLACEHOLDER_EMPTY_WORKFLOW_ID) {
+			try {
+				await workflowsStore.fetchActiveWorkflows();
+				const data = await workflowsStore.fetchWorkflow(workflowId.value);
+				workflowHelpers.initState(data);
+				await nodeHelpers.addNodes(data.nodes, data.connections);
+			} catch (error) {
+				toast.showError(error, i18n.baseText('nodeView.showError.openWorkflow.title'));
+			}
 		}
-	} else {
+
 		workflow.value = workflowsStore.getWorkflowById(workflowId.value);
+	} else {
+		workflow.value = workflowsStore.workflow;
 	}
 }
 
diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts
index d828fa58b4665..ae6ce96f70a68 100644
--- a/packages/workflow/src/Interfaces.ts
+++ b/packages/workflow/src/Interfaces.ts
@@ -1658,6 +1658,11 @@ export interface INodeTypeBaseDescription {
 	 * due to deprecation or as a special case (e.g. Start node)
 	 */
 	hidden?: true;
+
+	/**
+	 * Whether the node will be wrapped for tool-use by AI Agents
+	 */
+	usableAsTool?: true;
 }
 
 export interface INodePropertyRouting {
diff --git a/packages/workflow/src/NodeHelpers.ts b/packages/workflow/src/NodeHelpers.ts
index 635bccddfe93b..1a58de2302a3a 100644
--- a/packages/workflow/src/NodeHelpers.ts
+++ b/packages/workflow/src/NodeHelpers.ts
@@ -36,6 +36,7 @@ import type {
 	NodeParameterValue,
 	ResourceMapperValue,
 	INodeTypeDescription,
+	INodeTypeBaseDescription,
 	INodeOutputConfiguration,
 	INodeInputConfiguration,
 	GenericValue,
@@ -351,6 +352,58 @@ const declarativeNodeOptionParameters: INodeProperties = {
 	],
 };
 
+/**
+ * Determines if the node is of INodeType
+ */
+export function isINodeType(obj: unknown): obj is INodeType {
+	return typeof obj === 'object' && obj !== null && 'execute' in obj;
+}
+
+/**
+ * Modifies the description of the passed in object, such that it can be used
+ * as an AI Agent Tool.
+ * Returns the modified item (not copied)
+ */
+export function convertNodeToAiTool<
+	T extends object & { description: INodeTypeDescription | INodeTypeBaseDescription },
+>(item: T): T {
+	// quick helper function for typeguard down below
+	function isFullDescription(obj: unknown): obj is INodeTypeDescription {
+		return typeof obj === 'object' && obj !== null && 'properties' in obj;
+	}
+
+	if (isFullDescription(item.description)) {
+		item.description.name += 'Tool';
+		item.description.inputs = [];
+		item.description.outputs = [NodeConnectionType.AiTool];
+		item.description.displayName += ' Tool (wrapped)';
+		delete item.description.usableAsTool;
+		if (!item.description.properties.map((prop) => prop.name).includes('toolDescription')) {
+			const descProp: INodeProperties = {
+				displayName: 'Description',
+				name: 'toolDescription',
+				type: 'string',
+				default: item.description.description,
+				required: true,
+				typeOptions: { rows: 2 },
+				description:
+					'Explain to the LLM what this tool does, a good, specific description would allow LLMs to produce expected results much more often',
+				placeholder: `e.g. ${item.description.description}`,
+			};
+			item.description.properties.unshift(descProp);
+		}
+	}
+
+	item.description.codex = {
+		categories: ['AI'],
+		subcategories: {
+			AI: ['Tools'],
+			Tools: ['Other Tools'],
+		},
+	};
+	return item;
+}
+
 /**
  * Determines if the provided node type has any output types other than the main connection type.
  * @param typeDescription The node's type description to check.
diff --git a/packages/workflow/test/NodeHelpers.test.ts b/packages/workflow/test/NodeHelpers.test.ts
index 7dd360313026c..583d423c60c74 100644
--- a/packages/workflow/test/NodeHelpers.test.ts
+++ b/packages/workflow/test/NodeHelpers.test.ts
@@ -13,6 +13,7 @@ import {
 	isSingleExecution,
 	isSubNodeType,
 	applyDeclarativeNodeOptionParameters,
+	convertNodeToAiTool,
 } from '@/NodeHelpers';
 
 describe('NodeHelpers', () => {
@@ -3636,4 +3637,89 @@ describe('NodeHelpers', () => {
 			expect(nodeType.description.properties).toEqual([]);
 		});
 	});
+
+	describe('convertNodeToAiTool', () => {
+		let fullNodeWrapper: { description: INodeTypeDescription };
+
+		beforeEach(() => {
+			fullNodeWrapper = {
+				description: {
+					displayName: 'Test Node',
+					name: 'testNode',
+					group: ['test'],
+					description: 'A test node',
+					version: 1,
+					defaults: {},
+					inputs: [NodeConnectionType.Main],
+					outputs: [NodeConnectionType.Main],
+					properties: [],
+				},
+			};
+		});
+
+		it('should modify the name and displayName correctly', () => {
+			const result = convertNodeToAiTool(fullNodeWrapper);
+			expect(result.description.name).toBe('testNodeTool');
+			expect(result.description.displayName).toBe('Test Node Tool (wrapped)');
+		});
+
+		it('should update inputs and outputs', () => {
+			const result = convertNodeToAiTool(fullNodeWrapper);
+			expect(result.description.inputs).toEqual([]);
+			expect(result.description.outputs).toEqual([NodeConnectionType.AiTool]);
+		});
+
+		it('should remove the usableAsTool property', () => {
+			fullNodeWrapper.description.usableAsTool = true;
+			const result = convertNodeToAiTool(fullNodeWrapper);
+			expect(result.description.usableAsTool).toBeUndefined();
+		});
+
+		it("should add toolDescription property if it doesn't exist", () => {
+			const result = convertNodeToAiTool(fullNodeWrapper);
+			const toolDescriptionProp = result.description.properties.find(
+				(prop) => prop.name === 'toolDescription',
+			);
+			expect(toolDescriptionProp).toBeDefined();
+			expect(toolDescriptionProp?.type).toBe('string');
+			expect(toolDescriptionProp?.default).toBe(fullNodeWrapper.description.description);
+		});
+
+		it('should not add toolDescription property if it already exists', () => {
+			const toolDescriptionProp: INodeProperties = {
+				displayName: 'Tool Description',
+				name: 'toolDescription',
+				type: 'string',
+				default: 'Existing description',
+			};
+			fullNodeWrapper.description.properties = [toolDescriptionProp];
+			const result = convertNodeToAiTool(fullNodeWrapper);
+			expect(result.description.properties).toHaveLength(1);
+			expect(result.description.properties[0]).toEqual(toolDescriptionProp);
+		});
+
+		it('should set codex categories correctly', () => {
+			const result = convertNodeToAiTool(fullNodeWrapper);
+			expect(result.description.codex).toEqual({
+				categories: ['AI'],
+				subcategories: {
+					AI: ['Tools'],
+					Tools: ['Other Tools'],
+				},
+			});
+		});
+
+		it('should preserve existing properties', () => {
+			const existingProp: INodeProperties = {
+				displayName: 'Existing Prop',
+				name: 'existingProp',
+				type: 'string',
+				default: 'test',
+			};
+			fullNodeWrapper.description.properties = [existingProp];
+			const result = convertNodeToAiTool(fullNodeWrapper);
+			expect(result.description.properties).toHaveLength(2); // Existing prop + toolDescription
+			expect(result.description.properties).toContainEqual(existingProp);
+		});
+	});
 });
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7e993b188c05a..8895ba097b4b8 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -1017,6 +1017,9 @@ importers:
 
   packages/core:
     dependencies:
+      '@langchain/core':
+        specifier: 0.2.18
+        version: 0.2.18(langchain@0.2.11(axios@1.7.4)(openai@4.53.0))(openai@4.53.0)
       '@n8n/client-oauth2':
         specifier: workspace:*
         version: link:../@n8n/client-oauth2
@@ -1077,6 +1080,9 @@ importers:
       xml2js:
         specifier: 'catalog:'
         version: 0.6.2
+      zod:
+        specifier: 'catalog:'
+        version: 3.23.8
     devDependencies:
       '@types/aws4':
         specifier: ^1.5.1
@@ -21616,7 +21622,7 @@ snapshots:
 
   eslint-import-resolver-node@0.3.9:
     dependencies:
-      debug: 3.2.7(supports-color@8.1.1)
+      debug: 3.2.7(supports-color@5.5.0)
       is-core-module: 2.13.1
       resolve: 1.22.8
     transitivePeerDependencies:
@@ -21641,7 +21647,7 @@ snapshots:
 
   eslint-module-utils@2.8.0(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.2))(eslint-plugin-import@2.29.1)(eslint@8.57.0))(eslint@8.57.0):
     dependencies:
-      debug: 3.2.7(supports-color@8.1.1)
+      debug: 3.2.7(supports-color@5.5.0)
     optionalDependencies:
       '@typescript-eslint/parser': 7.2.0(eslint@8.57.0)(typescript@5.5.2)
       eslint: 8.57.0
@@ -21661,7 +21667,7 @@ snapshots:
       array.prototype.findlastindex: 1.2.3
       array.prototype.flat: 1.3.2
       array.prototype.flatmap: 1.3.2
-      debug: 3.2.7(supports-color@8.1.1)
+      debug: 3.2.7(supports-color@5.5.0)
       doctrine: 2.1.0
       eslint: 8.57.0
       eslint-import-resolver-node: 0.3.9
@@ -22531,7 +22537,7 @@ snapshots:
       array-parallel: 0.1.3
       array-series: 0.1.5
       cross-spawn: 4.0.2
-      debug: 3.2.7(supports-color@8.1.1)
+      debug: 3.2.7(supports-color@5.5.0)
     transitivePeerDependencies:
       - supports-color
 
@@ -25552,7 +25558,7 @@ snapshots:
 
   pdf-parse@1.1.1:
     dependencies:
-      debug: 3.2.7(supports-color@8.1.1)
+      debug: 3.2.7(supports-color@5.5.0)
       node-ensure: 0.0.0
     transitivePeerDependencies:
       - supports-color
@@ -26438,7 +26444,7 @@ snapshots:
 
   rhea@1.0.24:
     dependencies:
-      debug: 3.2.7(supports-color@8.1.1)
+      debug: 3.2.7(supports-color@5.5.0)
     transitivePeerDependencies:
       - supports-color