UNPKG

oneie

Version:

Build apps, websites, and AI agents in English. Zero-interaction setup for AI agents (Claude Code, Cursor, Windsurf). Download to your computer, run in the cloud, deploy to the edge. Open source and free forever.

1,034 lines (912 loc) 30.1 kB
--- title: N8N dimension: connections category: n8n.md tags: agent, ai, architecture related_dimensions: events, people, things scope: global created: 2025-11-03 updated: 2025-11-03 version: 1.0.0 ai_context: | This document is part of the connections dimension in the n8n.md category. Location: one/connections/n8n.md Purpose: Documents n8n integration - ai agent tool integration Related dimensions: events, people, things For AI agents: Read this to understand n8n. --- # N8N Integration - AI Agent Tool Integration **Version:** 1.0.0 **Purpose:** Enable AI agents to execute n8n workflows as tools, allowing seamless integration between ONE platform and n8n automation workflows --- ## Overview n8n is an extensible workflow automation tool that allows users to create complex automations connecting 400+ services. This integration enables: 1. **AI Agents n8n**: Agents can trigger n8n workflows as tools 2. **n8n AI Agents**: n8n workflows can call agent actions via webhooks 3. **Workflow as Tool**: Each n8n workflow becomes a callable tool for agents --- ## Architecture ``` ┌─────────────────────────────────────────────────────────────────┐ ONE Platform Agents (Strategy, Marketing, Sales, Service, etc.) └────────────────┬────────────────────────────────────────────────┘ Execute workflow as tool ┌───────────────┐ N8NService Effect.ts service (Layer) └───────┬───────┘ HTTP API call ┌───────────────────────┐ n8n Instance (Self-hosted/Cloud) └───────┬───────────────┘ Executes workflow ┌─────────────────────────────────────┐ External Services (400+ integrations)│ ├─ Google Sheets ├─ Airtable ├─ Slack ├─ Notion ├─ Zapier ├─ GitHub └─ Any HTTP API └─────────────────────────────────────┘ ``` --- ## Ontology Mapping ### Entity Types Used Uses the unified external integration schema from `Ontology.md`: ```typescript // EXTERNAL INTEGRATIONS (already in Ontology.md) | 'external_agent' // External AI agent (not used for n8n) | 'external_workflow' // External workflow (n8n, Zapier, Make, etc.) | 'external_connection' // Connection config to external service ``` ### Entity Properties **external_workflow Properties (for n8n workflows):** ```typescript { platform: "n8n", // Platform identifier workflowId: string, // n8n workflow ID name: string, // Workflow name description: string, // What the workflow does webhookUrl?: string, // Trigger URL active: boolean, // Is workflow active? tags: string[], // n8n tags/categories inputSchema: { // Expected input parameters [key: string]: { type: "string" | "number" | "boolean" | "object" | "array", required: boolean, description: string, default?: any } }, outputSchema: { // Expected output structure [key: string]: { type: "string" | "number" | "boolean" | "object" | "array", description: string } }, executionCount: number, // Total executions successRate: number, // 0.0 to 1.0 averageExecutionTime: number, // milliseconds lastExecutedAt?: number, createdAt: number, updatedAt: number } ``` **Workflow Execution Tracking:** Use events (not a separate entity type) to track executions: ```typescript // Execution as event { type: 'agent_executed', actorId: agentId, targetId: workflowId, // external_workflow entity timestamp: Date.now(), metadata: { action: 'workflow_executed', platform: 'n8n', executionId: 'exec-123', // n8n execution ID status: 'success', startedAt: number, finishedAt: number, executionTime: number, input: any, output: any, triggeredBy: { source: 'agent' | 'user' | 'cron' | 'webhook' } } } ``` **external_connection Properties (for n8n connection):** ```typescript { platform: "n8n", name: string, // Connection name baseUrl?: string, // n8n instance URL apiKey?: string, // Encrypted n8n API key websocketUrl?: string, // WebSocket endpoint (if supported) webhookSecret?: string, // Webhook signature secret connectionType: "rest" | "webhook", authentication: { type: "apiKey", credentials: any // Encrypted credentials }, status: "active" | "inactive" | "error", lastConnectedAt?: number, lastError?: string, linkedEntityIds: string[], // Connected workflow IDs rateLimits?: { requestsPerMinute: number, requestsPerDay: number }, createdAt: number, updatedAt: number } ``` ### Connection Types Use existing connection types: ```typescript // Agent n8n workflow { fromEntityId: agentId, toEntityId: workflowId, // external_workflow entity relationshipType: 'integrated_with', // Existing type metadata: { platform: 'n8n', integrationType: 'workflow', permissions: ['execute', 'view'], configuredAt: Date.now() } } ``` **Note:** Workflow executions are tracked as events, not as separate entities or connections. ### Event Types Use existing consolidated event types: ```typescript // Workflow executed successfully { type: 'agent_executed', // Existing type actorId: agentId, targetId: workflowId, // external_workflow entity timestamp: Date.now(), metadata: { action: 'workflow_executed', platform: 'n8n', executionId: 'exec-123', status: 'success', startedAt: 1234567890, finishedAt: 1234568890, executionTime: 1000, input: {...}, output: {...}, triggeredBy: { source: 'agent' } } } // Workflow failed { type: 'agent_failed', // Existing type actorId: agentId, targetId: workflowId, timestamp: Date.now(), metadata: { action: 'workflow_failed', platform: 'n8n', executionId: 'exec-124', error: 'Connection timeout', node: 'HTTP Request', triggeredBy: { source: 'agent' } } } ``` --- ## Effect.ts Service Implementation ### N8NProvider ```typescript import { Effect, Layer, Context } from 'effect'; import { HttpProvider } from './http'; export class N8NError extends Error { readonly _tag = 'N8NError'; constructor( public readonly message: string, public readonly workflowId?: string, public readonly statusCode?: number ) { super(message); } } export class N8NProvider extends Effect.Service<N8NProvider>()('N8NProvider', { effect: Effect.gen(function* () { const http = yield* HttpProvider; return { /** * Execute n8n workflow via webhook */ executeWorkflow: (args: { webhookUrl: string; input: Record<string, any>; timeout?: number; }) => Effect.gen(function* () { const result = yield* http.post({ url: args.webhookUrl, body: args.input, headers: { 'Content-Type': 'application/json', }, }).pipe( Effect.timeout(args.timeout ?? '30 seconds'), Effect.catchAll((error) => Effect.fail( new N8NError( `Workflow execution failed: ${error.message}`, undefined, error.statusCode ) ) ) ); return result.data; }), /** * Get workflow execution status via n8n API */ getExecutionStatus: (args: { baseUrl: string; apiKey: string; executionId: string; }) => Effect.gen(function* () { const result = yield* http.get({ url: `${args.baseUrl}/api/v1/executions/${args.executionId}`, headers: { 'X-N8N-API-KEY': args.apiKey, }, }).pipe( Effect.catchAll((error) => Effect.fail( new N8NError( `Failed to get execution status: ${error.message}`, undefined, error.statusCode ) ) ) ); return result.data as { id: string; finished: boolean; mode: string; startedAt: string; stoppedAt?: string; workflowData: any; data?: any; }; }), /** * List workflows from n8n instance */ listWorkflows: (args: { baseUrl: string; apiKey: string }) => Effect.gen(function* () { const result = yield* http.get({ url: `${args.baseUrl}/api/v1/workflows`, headers: { 'X-N8N-API-KEY': args.apiKey, }, }).pipe( Effect.catchAll((error) => Effect.fail( new N8NError( `Failed to list workflows: ${error.message}`, undefined, error.statusCode ) ) ) ); return result.data as Array<{ id: string; name: string; active: boolean; tags: Array<{ id: string; name: string }>; createdAt: string; updatedAt: string; }>; }), /** * Activate/deactivate workflow */ toggleWorkflow: (args: { baseUrl: string; apiKey: string; workflowId: string; active: boolean; }) => Effect.gen(function* () { yield* http.patch({ url: `${args.baseUrl}/api/v1/workflows/${args.workflowId}`, headers: { 'X-N8N-API-KEY': args.apiKey, 'Content-Type': 'application/json', }, body: { active: args.active, }, }).pipe( Effect.catchAll((error) => Effect.fail( new N8NError( `Failed to toggle workflow: ${error.message}`, args.workflowId, error.statusCode ) ) ) ); }), }; }), dependencies: [HttpProvider.Default], }) {} ``` ### N8NService (Business Logic) ```typescript import { Effect } from 'effect'; import { ConvexDatabase } from './convex-database'; import { N8NProvider } from './n8n-provider'; export class N8NService extends Effect.Service<N8NService>()('N8NService', { effect: Effect.gen(function* () { const db = yield* ConvexDatabase; const n8n = yield* N8NProvider; return { /** * Execute workflow as tool for AI agent */ executeWorkflowTool: (args: { agentId: Id<'entities'>; workflowId: Id<'entities'>; input: Record<string, any>; }) => Effect.gen(function* () { // 1. Get workflow entity const workflow = yield* db.get(args.workflowId).pipe( Effect.flatMap((w) => w?.type === 'n8n_workflow' ? Effect.succeed(w) : Effect.fail(new Error('Invalid workflow')) ) ); // 2. Validate input against schema yield* validateInput(args.input, workflow.properties.inputSchema); // 3. Create execution record const executionId = yield* db.insert('entities', { type: 'n8n_execution', name: `Execution ${Date.now()}`, properties: { workflowId: workflow.properties.workflowId, executionId: '', // Will be filled after n8n responds status: 'running', startedAt: Date.now(), input: args.input, triggeredBy: { agentId: args.agentId, source: 'agent', }, }, status: 'active', createdAt: Date.now(), updatedAt: Date.now(), }); // 4. Execute workflow via webhook const result = yield* n8n .executeWorkflow({ webhookUrl: workflow.properties.webhookUrl, input: args.input, timeout: 60000, // 1 minute }) .pipe( Effect.retry({ times: 2, schedule: Schedule.exponential('1 second') }), Effect.catchAll((error) => Effect.gen(function* () { // Update execution with error yield* db.patch(executionId, { properties: { ...workflow.properties, status: 'error', finishedAt: Date.now(), error: { message: error.message, }, }, }); // Log failure event yield* db.insert('events', { type: 'agent_failed', actorId: args.agentId, targetId: args.workflowId, timestamp: Date.now(), metadata: { action: 'workflow_failed', executionId, error: error.message, }, }); return Effect.fail(error); }) ) ); // 5. Update execution with success const finishedAt = Date.now(); yield* db.patch(executionId, { properties: { status: 'success', finishedAt, executionTime: finishedAt - workflow.properties.startedAt, output: result, }, }); // 6. Log success event yield* db.insert('events', { type: 'agent_executed', actorId: args.agentId, targetId: args.workflowId, timestamp: Date.now(), metadata: { action: 'workflow_executed', executionId, status: 'success', executionTime: finishedAt - workflow.properties.startedAt, input: args.input, output: result, }, }); // 7. Update workflow stats yield* db.patch(args.workflowId, { properties: { ...workflow.properties, executionCount: workflow.properties.executionCount + 1, lastExecutedAt: Date.now(), }, }); return { executionId, status: 'success' as const, output: result, }; }), /** * Sync workflows from n8n instance */ syncWorkflows: (args: { credentialId: Id<'entities'>; userId: Id<'entities'> }) => Effect.gen(function* () { // 1. Get credentials const credential = yield* db.get(args.credentialId).pipe( Effect.flatMap((c) => c?.type === 'n8n_credential' ? Effect.succeed(c) : Effect.fail(new Error('Invalid credential')) ) ); // 2. List workflows from n8n const workflows = yield* n8n.listWorkflows({ baseUrl: credential.properties.baseUrl, apiKey: credential.properties.apiKey, }); // 3. Create/update workflow entities const results = yield* Effect.all( workflows.map((w) => Effect.gen(function* () { // Check if workflow already exists const existing = yield* db .query('entities') .filter((q) => q.and( q.eq(q.field('type'), 'n8n_workflow'), q.eq(q.field('properties.workflowId'), w.id) ) ) .collect() .pipe(Effect.map((results) => results[0])); if (existing) { // Update existing yield* db.patch(existing._id, { properties: { ...existing.properties, name: w.name, active: w.active, tags: w.tags.map((t) => t.name), updatedAt: Date.now(), }, }); return existing._id; } else { // Create new const workflowId = yield* db.insert('entities', { type: 'n8n_workflow', name: w.name, properties: { workflowId: w.id, name: w.name, description: `n8n workflow: ${w.name}`, webhookUrl: `${credential.properties.baseUrl}/webhook/${w.id}`, active: w.active, tags: w.tags.map((t) => t.name), inputSchema: {}, // To be filled manually outputSchema: {}, executionCount: 0, successRate: 1.0, averageExecutionTime: 0, createdAt: Date.now(), updatedAt: Date.now(), }, status: w.active ? 'active' : 'inactive', createdAt: Date.now(), updatedAt: Date.now(), }); // Create connection yield* db.insert('connections', { fromEntityId: args.userId, toEntityId: workflowId, relationshipType: 'integrated_with', metadata: { integrationType: 'n8n_workflow', permissions: ['execute', 'view'], configuredAt: Date.now(), }, createdAt: Date.now(), }); return workflowId; } }) ), { concurrency: 5 } ); return { synced: results.length, workflowIds: results, }; }), }; }), dependencies: [ConvexDatabase.Default, N8NProvider.Default], }) {} // Helper: Validate input against schema function validateInput( input: Record<string, any>, schema: Record<string, any> ): Effect.Effect<void, Error> { return Effect.gen(function* () { for (const [key, fieldSchema] of Object.entries(schema)) { if (fieldSchema.required && !(key in input)) { yield* Effect.fail(new Error(`Missing required field: ${key}`)); } if (key in input && typeof input[key] !== fieldSchema.type) { yield* Effect.fail( new Error(`Invalid type for field ${key}: expected ${fieldSchema.type}`) ); } } }); } ``` --- ## Convex Function Wrappers ```typescript // convex/n8n.ts import { mutation, query } from './_generated/server'; import { v } from 'convex/values'; import { confect } from './confect'; import { N8NService } from './services/n8n'; import { MainLayer } from './layers'; /** * Execute n8n workflow */ export const executeWorkflow = confect.mutation({ args: { workflowId: v.id('entities'), input: v.any(), }, handler: (ctx, args) => Effect.gen(function* () { const userId = yield* getUserId(ctx); const service = yield* N8NService; return yield* service.executeWorkflowTool({ agentId: userId, // Or actual agent ID workflowId: args.workflowId, input: args.input, }); }).pipe(Effect.provide(MainLayer)), }); /** * Sync workflows from n8n instance */ export const syncWorkflows = confect.mutation({ args: { credentialId: v.id('entities'), }, handler: (ctx, args) => Effect.gen(function* () { const userId = yield* getUserId(ctx); const service = yield* N8NService; return yield* service.syncWorkflows({ credentialId: args.credentialId, userId, }); }).pipe(Effect.provide(MainLayer)), }); /** * Get workflow executions */ export const getWorkflowExecutions = query({ args: { workflowId: v.id('entities'), limit: v.optional(v.number()), }, handler: async (ctx, args) => { const executions = await ctx.db .query('entities') .filter((q) => q.and( q.eq(q.field('type'), 'n8n_execution'), q.eq(q.field('properties.workflowId'), args.workflowId) ) ) .order('desc') .take(args.limit ?? 50); return executions; }, }); ``` --- ## Usage Examples ### 1. Agent Using n8n Workflow as Tool ```typescript // Agent executes "Send Weekly Report" workflow export class ReportingAgent extends Effect.Service<ReportingAgent>()( 'ReportingAgent', { effect: Effect.gen(function* () { const n8n = yield* N8NService; return { sendWeeklyReport: (userId: Id<'entities'>) => Effect.gen(function* () { // Get user data const userData = yield* getUserMetrics(userId); // Execute n8n workflow const result = yield* n8n.executeWorkflowTool({ agentId: 'agent-reporting', workflowId: 'workflow-weekly-report', input: { userId, metrics: userData.metrics, period: 'week', }, }); return result; }), }; }), } ) {} ``` ### 2. Marketing Agent Using n8n for Email Campaign ```typescript export class MarketingAgent extends Effect.Service<MarketingAgent>()( 'MarketingAgent', { effect: Effect.gen(function* () { const n8n = yield* N8NService; return { launchEmailCampaign: (args: { campaignId: Id<'entities'> }) => Effect.gen(function* () { // Get campaign data const campaign = yield* db.get(args.campaignId); // Execute n8n "Email Campaign" workflow const result = yield* n8n.executeWorkflowTool({ agentId: 'agent-marketing', workflowId: 'workflow-email-campaign', input: { subject: campaign.properties.subject, recipientSegment: campaign.properties.segment, emailTemplate: campaign.properties.template, scheduledAt: campaign.properties.scheduledAt, }, }); return result; }), }; }), } ) {} ``` ### 3. Sync n8n Workflows on Onboarding ```typescript export const onboardN8N = confect.mutation({ args: { apiKey: v.string(), baseUrl: v.string(), instanceType: v.union(v.literal('cloud'), v.literal('self-hosted')), }, handler: (ctx, args) => Effect.gen(function* () { const userId = yield* getUserId(ctx); // 1. Create credential entity const credentialId = yield* db.insert('entities', { type: 'n8n_credential', name: 'n8n Credentials', properties: { apiKey: args.apiKey, baseUrl: args.baseUrl, instanceType: args.instanceType, active: true, createdAt: Date.now(), }, status: 'active', createdAt: Date.now(), updatedAt: Date.now(), }); // 2. Sync workflows const n8nService = yield* N8NService; const syncResult = yield* n8nService.syncWorkflows({ credentialId, userId, }); return { credentialId, ...syncResult, }; }).pipe(Effect.provide(MainLayer)), }); ``` --- ## Frontend Integration ### React Component for Workflow Execution ```tsx // src/components/n8n/WorkflowExecutor.tsx import { useMutation, useQuery } from 'convex/react'; import { api } from '@/convex/_generated/api'; import { Button } from '@/components/ui/button'; import { useState } from 'react'; export function WorkflowExecutor({ workflowId }: { workflowId: Id<'entities'> }) { const [input, setInput] = useState({}); const executeWorkflow = useMutation(api.n8n.executeWorkflow); const executions = useQuery(api.n8n.getWorkflowExecutions, { workflowId, limit: 10 }); return ( <div> <h2>Execute Workflow</h2> <textarea value={JSON.stringify(input, null, 2)} onChange={(e) => setInput(JSON.parse(e.target.value))} placeholder='{"key": "value"}' /> <Button onClick={() => executeWorkflow({ workflowId, input, }) } > Execute </Button> <h3>Recent Executions</h3> <ul> {executions?.map((exec) => ( <li key={exec._id}> {exec.properties.status} - {new Date(exec.properties.startedAt).toLocaleString()} </li> ))} </ul> </div> ); } ``` --- ## Benefits ### For AI Agents 1. **400+ Service Integrations**: Agents can interact with any service n8n supports 2. **No-Code Workflows**: Users can create complex workflows without code 3. **Visual Debugging**: n8n's UI shows workflow execution visually 4. **Composability**: Agents combine multiple n8n workflows as tools ### For Users 1. **Familiar Interface**: n8n's drag-and-drop workflow builder 2. **Extensive Library**: Pre-built templates for common tasks 3. **Self-Hosted Option**: Full control over data and workflows 4. **Bi-Directional Integration**: n8n can trigger agents AND agents can trigger n8n ### For Platform 1. **Extensibility**: Add integrations without building custom connectors 2. **User Empowerment**: Users extend platform capabilities themselves 3. **Ecosystem Growth**: Leverage n8n's 400+ integrations immediately 4. **Reduced Development**: Don't build integrations that n8n already has --- ## Common n8n Workflow Patterns ### 1. Data Sync Workflow **Purpose**: Sync data from Google Sheets to Convex ``` Trigger (Webhook) Google Sheets (Read) Convex Mutation Response ``` ### 2. Notification Workflow **Purpose**: Send multi-channel notifications ``` Trigger (Webhook) Split (Email/SMS/Slack) Send Notifications Aggregate Results ``` ### 3. Content Publishing Workflow **Purpose**: Publish content to multiple platforms ``` Trigger (Webhook) Twitter API LinkedIn API Instagram API Log Results ``` ### 4. Analytics Workflow **Purpose**: Aggregate analytics from multiple sources ``` Cron Trigger [Google Analytics, Stripe, Convex] Aggregate Store in DB ``` ### 5. Lead Enrichment Workflow **Purpose**: Enrich lead data from multiple sources ``` Trigger (New Lead) Clearbit API Hunter.io LinkedIn Update Convex ``` --- ## Testing ```typescript // tests/n8n.test.ts import { Effect, Layer } from 'effect'; import { N8NService } from '@/convex/services/n8n'; describe('N8NService', () => { it('should execute workflow successfully', async () => { const MockN8N = Layer.succeed(N8NProvider, { executeWorkflow: () => Effect.succeed({ success: true, data: { message: 'Workflow executed' }, }), }); const result = await Effect.runPromise( Effect.gen(function* () { const service = yield* N8NService; return yield* service.executeWorkflowTool({ agentId: 'agent-123', workflowId: 'workflow-456', input: { test: true }, }); }).pipe(Effect.provide(Layer.merge(MockN8N, MockDB))) ); expect(result.status).toBe('success'); }); }); ``` --- ## Security Considerations 1. **API Key Storage**: Encrypt n8n API keys in database 2. **Webhook Validation**: Validate webhook signatures from n8n 3. **Rate Limiting**: Prevent workflow execution abuse 4. **Input Sanitization**: Validate all inputs before sending to n8n 5. **Permission Checks**: Ensure user has permission to execute workflow 6. **Execution Limits**: Set timeout and retry limits --- ## Summary This integration enables: - **AI Agents n8n**: Agents execute workflows as tools - **n8n AI Agents**: Workflows trigger agent actions - **100% Effect.ts**: Typed errors, automatic retry, composable - **Non-Breaking**: Uses existing connection/event types - **Minimal Entities**: Only 3 new entity types needed - **Extensible**: Easy to add more n8n features later **Next Steps**: 1. Add 3 entity types to Ontology.md 2. Implement N8NProvider and N8NService 3. Create Convex function wrappers 4. Build frontend workflow management UI 5. Test with common workflows