diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml index d72ced902c8..1688bcf267f 100644 --- a/.github/workflows/test-build.yml +++ b/.github/workflows/test-build.yml @@ -102,6 +102,7 @@ jobs: bunx vitest run scripts/retired-columns.postgres.test.ts scripts/connector-sync-schedule-precision.postgres.test.ts + scripts/database-failure-classification.postgres.test.ts - name: Verify OAuth lifecycle and SCIM membership guards in PostgreSQL working-directory: apps/sim @@ -267,6 +268,7 @@ jobs: lib/knowledge/__integration__/search-source-pagination.integration.ts lib/knowledge/__integration__/search-reference-batching.integration.ts lib/knowledge/__integration__/embedding-insert-batches.integration.ts + lib/knowledge/__integration__/processing-lock-scope.integration.ts lib/knowledge/__integration__/connector-lifecycle-locks.integration.ts lib/knowledge/__integration__/connector-deferral.integration.ts lib/knowledge/__integration__/stored-document-recovery.integration.ts @@ -275,10 +277,13 @@ jobs: lib/knowledge/__integration__/listing-continuation.integration.ts lib/knowledge/__integration__/member-scope-renewal.integration.ts lib/knowledge/__integration__/member-document-lifecycle.integration.ts + lib/knowledge/__integration__/connector-lease-pages.integration.ts lib/knowledge/__integration__/slack-empty-threads.integration.ts lib/knowledge/__integration__/kb-block-search.integration.ts lib/knowledge/__integration__/gitlab-workspace.integration.ts lib/knowledge/__integration__/unfilled-projection-source.integration.ts + lib/knowledge/__integration__/knowledge-projection.integration.ts + lib/knowledge/__integration__/async-projection-processing.integration.ts lib/knowledge/__integration__/purged-detach-reservation.integration.ts lib/core/outbox/service.integration.ts lib/knowledge/__integration__/connector-upload.integration.ts diff --git a/apps/desktop/e2e/smoke.spec.ts b/apps/desktop/e2e/smoke.spec.ts index f6799b7a835..08ace958c77 100644 --- a/apps/desktop/e2e/smoke.spec.ts +++ b/apps/desktop/e2e/smoke.spec.ts @@ -21,9 +21,13 @@ const PAGES: Record = { '/login': '

fixture-login

', } +/** `User-Agent` of every request the fixture origin has served, in arrival order. */ +const requestUserAgents: string[] = [] + function startFixtureServer(): Promise<{ server: Server; origin: string }> { return new Promise((resolvePromise) => { const server = createServer((request, response) => { + requestUserAgents.push(request.headers['user-agent'] ?? '') const path = new URL(request.url ?? '/', 'http://127.0.0.1').pathname const sessionCookie = request.headers.cookie ?.split(';') @@ -85,6 +89,21 @@ test.describe('desktop shell smoke', () => { expect(window.url()).toBe(`${origin}/home`) }) + test('presents one stock Chrome user agent on every request from the first load', async () => { + requestUserAgents.length = 0 + app = await launchApp(origin) + const window = await app.firstWindow() + await expect(window.locator('#app')).toHaveText('fixture-app') + await window.evaluate(() => fetch('/home').then((response) => response.text())) + + const pageUserAgent = await window.evaluate(() => navigator.userAgent) + expect(pageUserAgent).toMatch( + /^Mozilla\/5\.0 \(.+\) AppleWebKit\/537\.36 \(KHTML, like Gecko\) Chrome\/\d+\.0\.0\.0 Safari\/537\.36$/ + ) + expect(requestUserAgents.length).toBeGreaterThanOrEqual(2) + expect(new Set(requestUserAgents)).toEqual(new Set([pageUserAgent])) + }) + test('internal window.open creates an independent full Sim window', async () => { app = await launchApp(origin) const window = await app.firstWindow() diff --git a/apps/desktop/src/main/browser-agent/session.test.ts b/apps/desktop/src/main/browser-agent/session.test.ts index 39e1bc2683b..a8f5603f594 100644 --- a/apps/desktop/src/main/browser-agent/session.test.ts +++ b/apps/desktop/src/main/browser-agent/session.test.ts @@ -37,6 +37,7 @@ interface MockView { session: { setPermissionRequestHandler: ReturnType setPermissionCheckHandler: ReturnType + setUserAgent: ReturnType webRequest: { onBeforeRequest: ReturnType } } on: ReturnType @@ -358,15 +359,14 @@ describe('browser-agent session', () => { expect(onTabNavigated).toHaveBeenCalledWith(contents, true) }) - it('gives every tab a user agent with no Electron token in it', () => { + it('leaves every tab on the process-wide user agent instead of overriding it', () => { const first = session.ensureTab() const second = session.addTab() for (const tab of [first, second]) { const contents = (tab.view as unknown as MockView).webContents - const agent = contents.setUserAgent.mock.calls.at(-1)?.[0] as string | undefined - expect(agent).toMatch(/^Mozilla\/5\.0 \(.+\) .*Chrome\/\d+\.0\.0\.0 Safari\/537\.36$/) - expect(agent).not.toMatch(/Electron|Sim\//) + expect(contents.setUserAgent).not.toHaveBeenCalled() + expect(contents.session.setUserAgent).not.toHaveBeenCalled() } }) diff --git a/apps/desktop/src/main/browser-agent/session.ts b/apps/desktop/src/main/browser-agent/session.ts index e183137c9a0..1e106142eae 100644 --- a/apps/desktop/src/main/browser-agent/session.ts +++ b/apps/desktop/src/main/browser-agent/session.ts @@ -67,7 +67,6 @@ import { isBlockedSubresourceUrl, subresourceNeedsResolution, } from '@/main/browser-agent/url-guard' -import { browserUserAgent } from '@/main/browser-agent/user-agent' import type { BrowserSessionSnapshot } from '@/main/desktop-chat-session-store' import { suggestedFilename, uniqueDownloadPath } from '@/main/downloads' import { @@ -1413,11 +1412,6 @@ function configureAgentPartition(ses: Session): void { } return ALLOWED_SITE_PERMISSIONS.has(permission) }) - // Service workers do not inherit a tab's user agent. With only the tab's set, - // the document request carries the browser string while the worker's own - // script request still announces Electron — and on a site that routes its - // fetches through a worker, that is the one the server sees. - ses.setUserAgent(browserUserAgent()) // SSRF choke point for the agent partition. Document navigations (top-level + // iframes) get the full DNS-resolving check — the one seam every navigation // passes through, including page-initiated ones the driver never sees (server @@ -1965,10 +1959,6 @@ function initializeTabView(view: WebContentsView, scopeId: string): WebContentsV const contents = view.webContents registerAgentWebContents(contents) configureAgentPartition(contents.session) - // The session default does not reach a WebContents that already exists, and - // the first tab is what brings the session into being, so each tab sets its - // own as well — otherwise tab one browses as Electron and the rest as Chrome. - contents.setUserAgent(browserUserAgent()) attachAgentContextMenu(contents, { addToChat: (text) => withBrowserScope(scopeId, () => addPageSelectionToChat(contents, text)), openTab: (url) => withBrowserScope(scopeId, () => openTabWithUrl(url, { agentOwned: false })), diff --git a/apps/desktop/src/main/index.ts b/apps/desktop/src/main/index.ts index 30972db3c9e..18996aedcce 100644 --- a/apps/desktop/src/main/index.ts +++ b/apps/desktop/src/main/index.ts @@ -83,6 +83,7 @@ import { attachTelemetryPolicy } from '@/main/telemetry-policy' import { TerminalRegistry } from '@/main/terminal/registry' import { installTray, type TrayHandle } from '@/main/tray' import { checkForUpdatesInteractive, initUpdater, type UpdaterHandle } from '@/main/updater' +import { installBrowserUserAgent } from '@/main/user-agent' import { createMainWindow, setupPermissionHandlers } from '@/main/window' import { attachWindowOpenPolicy, isPopupContents } from '@/main/windows' @@ -899,6 +900,7 @@ app.setName(APP_NAME_FOR_CHANNEL[channelForOrigin(DEFAULT_ORIGIN)]) if (process.env.SIM_DESKTOP_USER_DATA) { app.setPath('userData', process.env.SIM_DESKTOP_USER_DATA) } +installBrowserUserAgent() // The scheme the offline page and server picker load from must be declared // before the app is ready; the per-session handlers attach later. diff --git a/apps/desktop/src/main/browser-agent/user-agent.test.ts b/apps/desktop/src/main/user-agent.test.ts similarity index 69% rename from apps/desktop/src/main/browser-agent/user-agent.test.ts rename to apps/desktop/src/main/user-agent.test.ts index db63efa7141..423ef6600d1 100644 --- a/apps/desktop/src/main/browser-agent/user-agent.test.ts +++ b/apps/desktop/src/main/user-agent.test.ts @@ -1,11 +1,13 @@ import { app } from 'electron' import { describe, expect, it, vi } from 'vitest' -import { browserUserAgent, stockChromeUserAgent } from '@/main/browser-agent/user-agent' +import { installBrowserUserAgent, stockChromeUserAgent } from '@/main/user-agent' vi.mock('electron', () => import('@/test/electron-mock')) const ELECTRON_DEFAULT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Sim/1.0.0 Chrome/140.0.7339.207 Electron/43.1.1 Safari/537.36' +const STOCK_CHROME = + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36' describe('stockChromeUserAgent', () => { it('drops the application and Electron tokens a browser allowlist rejects', () => { @@ -15,9 +17,7 @@ describe('stockChromeUserAgent', () => { }) it('reproduces the desktop string Chrome sends under user-agent reduction', () => { - expect(stockChromeUserAgent(ELECTRON_DEFAULT)).toBe( - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36' - ) + expect(stockChromeUserAgent(ELECTRON_DEFAULT)).toBe(STOCK_CHROME) }) it('keeps the platform token of the machine it is running on', () => { @@ -32,12 +32,13 @@ describe('stockChromeUserAgent', () => { }) }) -describe('browserUserAgent', () => { - it('derives from the string Electron would otherwise have sent', () => { +describe('installBrowserUserAgent', () => { + it('idempotently makes stock Chrome the process-wide fallback every request path uses', () => { app.userAgentFallback = ELECTRON_DEFAULT - expect(browserUserAgent()).toBe( - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36' - ) + installBrowserUserAgent() + installBrowserUserAgent() + + expect(app.userAgentFallback).toBe(STOCK_CHROME) }) }) diff --git a/apps/desktop/src/main/browser-agent/user-agent.ts b/apps/desktop/src/main/user-agent.ts similarity index 77% rename from apps/desktop/src/main/browser-agent/user-agent.ts rename to apps/desktop/src/main/user-agent.ts index 9e988c67987..d14e5c80f1d 100644 --- a/apps/desktop/src/main/browser-agent/user-agent.ts +++ b/apps/desktop/src/main/user-agent.ts @@ -1,5 +1,6 @@ /** - * The user agent the browser resource presents to sites. + * The user agent the whole desktop process presents: the embedded browser and + * the app's own windows alike (desktop identity travels in `X-Sim-Client-Info`). * * Electron's default string carries two tokens no browser sends — * `Sim/` and `Electron/`. Chromium's own token sits right @@ -38,9 +39,11 @@ export function stockChromeUserAgent(defaultUserAgent: string): string { } /** - * Derived from the string Electron would otherwise have sent, so the reported - * Chromium version tracks whatever Chromium the app actually ships. + * Sets the stock Chrome identity as `app.userAgentFallback` before any session + * exists. Session and per-tab overrides miss some request paths (a cross-origin + * challenge frame still sends the process default), and a site that sees two + * user agents in one challenge rejects it as a spoof. Idempotent. */ -export function browserUserAgent(): string { - return stockChromeUserAgent(app.userAgentFallback) +export function installBrowserUserAgent(): void { + app.userAgentFallback = stockChromeUserAgent(app.userAgentFallback) } diff --git a/apps/docs/content/docs/platform/self-hosting/background-jobs.mdx b/apps/docs/content/docs/platform/self-hosting/background-jobs.mdx index 385ad1175e4..33c75b8f5de 100644 --- a/apps/docs/content/docs/platform/self-hosting/background-jobs.mdx +++ b/apps/docs/content/docs/platform/self-hosting/background-jobs.mdx @@ -47,6 +47,7 @@ Point cron at an **internal** address where possible (the in-cluster Service, or | Time pause/resume | `/api/resume/poll` | `*/1 * * * *` | Workflows paused on a timer | | Outbox processing | `/api/webhooks/outbox/process` | `*/1 * * * *` | Transactional-outbox retries for billing, membership, enterprise issuance, and workflow-deployment side effects | | Workspace file search dispatch | `/api/cron/workspace-file-search-dispatch` | `*/1 * * * *` | Dispatches indexing work for workspace file search | +| Knowledge projection | `/api/cron/knowledge-projection` | `*/1 * * * *` | Brings knowledge base search up to date with document, permission, and chunk changes | | Connector sync | `/api/knowledge/connectors/sync` | `*/5 * * * *` | Knowledge base connector syncs | | Connector member sync | `/api/knowledge/connectors/member-sync` | `*/5 * * * *` | Per-member access sync for permission-aware connectors | | Connector directory sync | `/api/knowledge/connectors/directory-sync` | `*/5 * * * *` | Refreshes the directory groups administrator-mode connectors mirror, so a membership change takes effect without waiting for a content sync | diff --git a/apps/sim/app/api/cron/knowledge-projection/route.test.ts b/apps/sim/app/api/cron/knowledge-projection/route.test.ts new file mode 100644 index 00000000000..2a8c5be05c8 --- /dev/null +++ b/apps/sim/app/api/cron/knowledge-projection/route.test.ts @@ -0,0 +1,86 @@ +/** + * @vitest-environment node + */ +import { createMockRequest } from '@sim/testing' +import { beforeEach, describe, expect, it, vi } from 'vitest' + +const mocks = vi.hoisted(() => ({ + enqueueSweep: vi.fn(), + verifyCronAuth: vi.fn(), +})) + +vi.mock('@/lib/auth/internal', () => ({ verifyCronAuth: mocks.verifyCronAuth })) +vi.mock('@/lib/knowledge/projection/enqueue', () => ({ + enqueueKnowledgeProjectionSweep: mocks.enqueueSweep, +})) + +import { GET } from '@/app/api/cron/knowledge-projection/route' + +function request() { + return createMockRequest( + 'GET', + undefined, + {}, + 'http://localhost:3000/api/cron/knowledge-projection' + ) +} + +describe('knowledge projection sweep route', () => { + beforeEach(() => { + vi.clearAllMocks() + mocks.verifyCronAuth.mockReturnValue(null) + }) + + it('returns as soon as Trigger.dev accepts the pass', async () => { + mocks.enqueueSweep.mockResolvedValue({ + triggered: true, + backend: 'trigger-dev', + jobId: 'run-1', + }) + + const response = await GET(request()) + + expect(response.status).toBe(202) + await expect(response.json()).resolves.toEqual({ + success: true, + triggered: true, + backend: 'trigger-dev', + jobId: 'run-1', + }) + }) + + it('answers 200 without a pass when the projector has nothing to do', async () => { + mocks.enqueueSweep.mockResolvedValue({ triggered: false, backend: null, jobId: null }) + + const response = await GET(request()) + + expect(response.status).toBe(200) + await expect(response.json()).resolves.toEqual({ + success: true, + triggered: false, + backend: null, + jobId: null, + }) + }) + + it('returns the cron auth refusal without enqueueing', async () => { + mocks.verifyCronAuth.mockReturnValue(new Response(null, { status: 401 })) + + const response = await GET(request()) + + expect(response.status).toBe(401) + expect(mocks.enqueueSweep).not.toHaveBeenCalled() + }) + + it('fails closed when Trigger.dev does not accept the pass', async () => { + mocks.enqueueSweep.mockRejectedValue(new Error('trigger unavailable')) + + const response = await GET(request()) + + expect(response.status).toBe(500) + await expect(response.json()).resolves.toEqual({ + success: false, + error: 'Sweep enqueue failed', + }) + }) +}) diff --git a/apps/sim/app/api/cron/knowledge-projection/route.ts b/apps/sim/app/api/cron/knowledge-projection/route.ts new file mode 100644 index 00000000000..5e5369afcce --- /dev/null +++ b/apps/sim/app/api/cron/knowledge-projection/route.ts @@ -0,0 +1,29 @@ +import { createLogger } from '@sim/logger' +import { getErrorMessage } from '@sim/utils/errors' +import { type NextRequest, NextResponse } from 'next/server' +import { verifyCronAuth } from '@/lib/auth/internal' +import { withRouteHandler } from '@/lib/core/utils/with-route-handler' +import { enqueueKnowledgeProjectionSweep } from '@/lib/knowledge/projection/enqueue' + +const logger = createLogger('KnowledgeProjectionSweepRoute') + +export const dynamic = 'force-dynamic' +export const maxDuration = 60 + +/** + * The knowledge projector's periodic sweep: enqueues one pass per window while there is work, and + * returns once Trigger.dev accepts it. Writers ask for passes as they commit; this converges + * whatever those requests missed. + */ +export const GET = withRouteHandler(async (request: NextRequest) => { + const authError = verifyCronAuth(request, 'Knowledge projection sweep') + if (authError) return authError + + try { + const result = await enqueueKnowledgeProjectionSweep() + return NextResponse.json({ success: true, ...result }, { status: result.triggered ? 202 : 200 }) + } catch (error) { + logger.error('Knowledge projection sweep enqueue failed', { error: getErrorMessage(error) }) + return NextResponse.json({ success: false, error: 'Sweep enqueue failed' }, { status: 500 }) + } +}) diff --git a/apps/sim/app/api/knowledge/connectors/member-sync/route.test.ts b/apps/sim/app/api/knowledge/connectors/member-sync/route.test.ts index c045b61d8c9..0dba1d286f2 100644 --- a/apps/sim/app/api/knowledge/connectors/member-sync/route.test.ts +++ b/apps/sim/app/api/knowledge/connectors/member-sync/route.test.ts @@ -101,6 +101,21 @@ describe('member sync scheduler owner routing', () => { ).toBe(true) }) + it('still dispatches due connectors when the stale observation sweep fails', async () => { + mocks.sweep.mockRejectedValue( + Object.assign(new Error('canceling statement due to lock timeout'), { code: '55P03' }) + ) + queueTableRows(schemaMock.knowledgeConnector, [ + { id: 'workspace-source', workspaceId: 'workspace-a', organizationId: null }, + ]) + const response = await GET(createMockRequest('GET')) + expect(response.status).toBe(200) + expect(mocks.dispatch).toHaveBeenCalledExactlyOnceWith( + 'workspace-source', + expect.objectContaining({ requireRunnable: true }) + ) + }) + it('preserves workspace dispatch and refuses absent or ambiguous ownership', async () => { queueTableRows(schemaMock.knowledgeConnector, [ { id: 'missing', workspaceId: null, organizationId: null }, diff --git a/apps/sim/app/api/knowledge/connectors/member-sync/route.ts b/apps/sim/app/api/knowledge/connectors/member-sync/route.ts index 10088e0cb14..6d1403f0106 100644 --- a/apps/sim/app/api/knowledge/connectors/member-sync/route.ts +++ b/apps/sim/app/api/knowledge/connectors/member-sync/route.ts @@ -1,6 +1,7 @@ import { db } from '@sim/db' import { knowledgeBase, knowledgeConnector, knowledgeConnectorMemberSyncLog } from '@sim/db/schema' import { createLogger } from '@sim/logger' +import { getErrorMessage } from '@sim/utils/errors' import { and, asc, eq, inArray, isNull, lte, type SQL, sql } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { verifyCronAuth } from '@/lib/auth/internal' @@ -157,9 +158,16 @@ export const GET = withRouteHandler(async (request: NextRequest) => { logger.warn(`[${requestId}] Closed ${closedLogs.length} orphaned member sync log(s)`) } - const sweep = await sweepStaleMemberObservations(now) - if (sweep.members > 0) { - logger.warn(`[${requestId}] Swept observations of ${sweep.members} stale member(s)`, sweep) + /** Observation hygiene never holds back dispatch; an unfinished sweep resumes next tick. */ + try { + const sweep = await sweepStaleMemberObservations(now) + if (sweep.members > 0) { + logger.warn(`[${requestId}] Swept observations of ${sweep.members} stale member(s)`, sweep) + } + } catch (error) { + logger.error(`[${requestId}] Stale member observation sweep failed`, { + error: getErrorMessage(error), + }) } const dueConnectors = await db diff --git a/apps/sim/app/api/knowledge/utils.test.ts b/apps/sim/app/api/knowledge/utils.test.ts index d957ff866c3..b0177b13b1e 100644 --- a/apps/sim/app/api/knowledge/utils.test.ts +++ b/apps/sim/app/api/knowledge/utils.test.ts @@ -211,6 +211,8 @@ describe('Knowledge Utils', () => { secretProvenanceVersion: null, }, ]) + /** Pre-commit connector and knowledge-base activity check. */ + queueTableRows(schemaMock.document, [{ id: 'doc1' }]) /** In-transaction active-document recheck. */ queueTableRows(schemaMock.document, [{ id: 'doc1' }]) diff --git a/apps/sim/app/api/organizations/[id]/usage/summary/route.ts b/apps/sim/app/api/organizations/[id]/usage/overview/route.ts similarity index 62% rename from apps/sim/app/api/organizations/[id]/usage/summary/route.ts rename to apps/sim/app/api/organizations/[id]/usage/overview/route.ts index 4d1111e1962..8294ead619c 100644 --- a/apps/sim/app/api/organizations/[id]/usage/summary/route.ts +++ b/apps/sim/app/api/organizations/[id]/usage/overview/route.ts @@ -1,24 +1,19 @@ -import { getOrganizationUsageSummaryContract } from '@/lib/api/contracts/organization-usage' +import { getOrganizationUsageOverviewContract } from '@/lib/api/contracts/organization-usage' import { defineInternalJsonRoute, internalRateLimits, internalSessionAuth, } from '@/lib/api/server/routes' -import { getOrganizationUsageSummary } from '@/lib/billing/application/organization-usage/get-organization-usage-summary' +import { getOrganizationUsageOverview } from '@/lib/billing/application/organization-usage/get-organization-usage-overview' import { organizationUsageOperations } from '@/lib/billing/application/organization-usage/operations' import { organizationUsageErrorPolicy } from '@/app/api/organizations/[id]/usage/error-policy' export const dynamic = 'force-dynamic' -/** - * Everything above the fold in one round trip. Kept separate from the breakdown - * route because every read here is index-covered, and folding in a dimension that - * heap-scans would put that cost on first paint. - */ export const GET = defineInternalJsonRoute({ - contract: getOrganizationUsageSummaryContract, + contract: getOrganizationUsageOverviewContract, auth: internalSessionAuth, - operation: organizationUsageOperations.readSummary, + operation: organizationUsageOperations.readOverview, rateLimit: internalRateLimits.none({ reason: 'Authenticated org-admin settings read, gated on enterprise entitlement and billing authority', @@ -32,6 +27,6 @@ export const GET = defineInternalJsonRoute({ endDate: query.endDate ? new Date(query.endDate) : undefined, timezone: query.timezone, }), - useCase: getOrganizationUsageSummary, + useCase: getOrganizationUsageOverview, present: (result) => result, }) diff --git a/apps/sim/app/api/v2/organizations/[organizationId]/usage/route.test.ts b/apps/sim/app/api/v2/organizations/[organizationId]/usage/route.test.ts index aa72574f436..0336f0878f4 100644 --- a/apps/sim/app/api/v2/organizations/[organizationId]/usage/route.test.ts +++ b/apps/sim/app/api/v2/organizations/[organizationId]/usage/route.test.ts @@ -57,7 +57,7 @@ vi.mock('@/lib/billing/organizations/member-limits', () => ({ vi.mock('@/lib/billing/core/usage-analytics-queries', () => ({ readUsageTotals: mocks.totals, readUsageTimeSeries: mocks.series, - readUsageBreakdown: mocks.breakdown, + readUsageGroups: mocks.breakdown, readUsageEntityNames: vi.fn().mockResolvedValue(new Map()), })) vi.mock('@/lib/billing/core/usage-log', () => ({ getBillingEntityUsageLogs: mocks.logs })) @@ -431,7 +431,9 @@ describe('organization usage API authorization and bounds', () => { ) const response = await breakdown(request('usage/breakdown?dimension=member'), usageContext) expect(response.status).toBe(413) - expect(mocks.breakdown).toHaveBeenCalledWith(expect.any(Array), 'member', undefined, 10_000) + expect(mocks.breakdown).toHaveBeenCalledWith( + expect.objectContaining({ dimension: 'member', maxRows: 10_000 }) + ) }) }) diff --git a/apps/sim/background/knowledge-processing.test.ts b/apps/sim/background/knowledge-processing.test.ts index e9c327d988f..6da47b1438e 100644 --- a/apps/sim/background/knowledge-processing.test.ts +++ b/apps/sim/background/knowledge-processing.test.ts @@ -46,6 +46,10 @@ import { MAX_PROVIDER_CONTINUATION_ATTEMPTS } from '@/lib/knowledge/documents/pr import { MAX_QUOTA_CONTINUATION_ATTEMPTS } from '@/lib/knowledge/documents/processing-quota-continuation' import type { DocumentProcessingAttemptContext } from '@/lib/knowledge/documents/service' import { + DOCUMENT_PROCESSING_RETRY_POLICY, + DocumentProcessingDatabaseRetryError, + getDocumentProcessingRetry, + processDocument, resolveQuotaContinuationDelayMs, runDocumentProcessing, } from '@/background/knowledge-processing' @@ -607,10 +611,83 @@ describe('knowledge processing worker', () => { ) expect(failure).toBeInstanceOf(Error) expect(failure).toMatchObject({ message: 'Database request failed (SQLSTATE 57014).' }) - expect(failure).not.toHaveProperty('cause') + /** Trigger records only the name, message and stack; the cause stays for classification. */ + expect((failure as Error).cause).toBe(error) + expect((failure as Error).stack).not.toContain('private') expect(JSON.stringify(failure)).not.toContain('private') }) + describe('transient database failures', () => { + const MINUTE = 60 * 1000 + const statementTimeout = () => + new DrizzleQueryError( + 'insert private SQL', + ['private bound content'], + Object.assign(new Error('canceling statement due to statement timeout'), { + code: '57014', + }) + ) + + /** A service failure that asks the worker whether to schedule a database retry, as the service does. */ + function failProcessingWith(error: Error): { scheduled: Array } { + const scheduled: Array = [] + mockProcessDocumentAsync.mockImplementation(async (...args: unknown[]) => { + const context = args[6] as DocumentProcessingAttemptContext + scheduled.push(context.scheduleDatabaseRetry?.(error) ?? null) + throw error + }) + return { scheduled } + } + + it('schedules a minute-scale retry and hands Trigger the same time the document records', async () => { + const error = statementTimeout() + const { scheduled } = failProcessingWith(error) + const startedAt = Date.now() + + const failure = await runDocumentProcessing(WORKSPACE_PAYLOAD, 1).catch( + (caught: unknown) => caught + ) + + expect(failure).toBeInstanceOf(DocumentProcessingDatabaseRetryError) + expect(failure).toMatchObject({ message: 'Database request failed (SQLSTATE 57014).' }) + expect((failure as Error).cause).toBe(error) + expect((failure as Error).stack).not.toContain('private') + const retryAt = scheduled[0] + expect(retryAt).toBeInstanceOf(Date) + expect(retryAt!.getTime() - startedAt).toBeGreaterThanOrEqual(2 * MINUTE * 0.8) + expect(retryAt!.getTime() - startedAt).toBeLessThanOrEqual(2 * MINUTE * 1.2 + 1000) + expect(getDocumentProcessingRetry(failure, 1)).toEqual({ retryAt }) + }) + + it('records the failure and stops once the database attempts are spent', async () => { + const error = statementTimeout() + const { scheduled } = failProcessingWith(error) + const lastAttempt = DOCUMENT_PROCESSING_RETRY_POLICY.database.maxAttempts + + const failure = await runDocumentProcessing(WORKSPACE_PAYLOAD, lastAttempt).catch( + (caught: unknown) => caught + ) + + expect(scheduled).toEqual([null]) + expect(failure).not.toBeInstanceOf(DocumentProcessingDatabaseRetryError) + expect((failure as Error).cause).toBe(error) + expect(getDocumentProcessingRetry(failure, lastAttempt)).toEqual({ skipRetrying: true }) + }) + + it('leaves other failures on the task retry settings and attempt count', async () => { + const error = new Error('Storage request timed out') + const { scheduled } = failProcessingWith(error) + + await expect(runDocumentProcessing(WORKSPACE_PAYLOAD, 1)).rejects.toBe(error) + + expect(scheduled).toEqual([null]) + expect(getDocumentProcessingRetry(error, 1)).toBeUndefined() + expect( + getDocumentProcessingRetry(error, DOCUMENT_PROCESSING_RETRY_POLICY.maxAttempts) + ).toEqual({ skipRetrying: true }) + }) + }) + it('retries failed provider continuation dispatch instead of reporting a successful deferral', async () => { const error = new Error('Trigger dispatch unavailable') mockTrigger.mockRejectedValue(error) @@ -745,11 +822,29 @@ describe('knowledge-process-document task configuration', () => { * `attempt_count = 1`, so each was left `failed` having never been retried. */ it('escalates to a larger machine on an out-of-memory kill', async () => { - const { processDocument } = await import('@/background/knowledge-processing') - expect(processDocument.retry?.outOfMemory?.machine).toBe('large-2x') }) + it('declares enough attempts for database retries and routes failures through catchError', async () => { + expect(processDocument.retry?.maxAttempts).toBe( + Math.max( + DOCUMENT_PROCESSING_RETRY_POLICY.maxAttempts, + DOCUMENT_PROCESSING_RETRY_POLICY.database.maxAttempts + ) + ) + const retryAt = new Date('2026-01-01T00:02:00.000Z') + const scheduled = new DocumentProcessingDatabaseRetryError( + 'Database request failed.', + retryAt, + { + cause: new Error('private'), + } + ) + await expect( + processDocument.catchError?.({ error: scheduled, ctx: { attempt: { number: 1 } } } as never) + ).resolves.toEqual({ retryAt }) + }) + it('backs durable quota continuations off to a bounded polling interval', () => { const first = resolveQuotaContinuationDelayMs(1) const second = resolveQuotaContinuationDelayMs(2) diff --git a/apps/sim/background/knowledge-processing.ts b/apps/sim/background/knowledge-processing.ts index 1964e315958..e4352cf4968 100644 --- a/apps/sim/background/knowledge-processing.ts +++ b/apps/sim/background/knowledge-processing.ts @@ -1,6 +1,14 @@ import { createLogger } from '@sim/logger' +import { findCause } from '@sim/utils/errors' import { queue, task } from '@trigger.dev/sdk' import { env, envNumber } from '@/lib/core/config/env' +import { + type BackgroundRetryDecision, + type BackgroundRetryPolicy, + backgroundRetryAttemptCeiling, + getBackgroundRetryDecision, + getDatabaseRetryAt, +} from '@/lib/core/errors/background-retry' import { BYOK_EMBEDDING_CREDENTIAL_REJECTION_MESSAGE, EMBEDDING_QUOTA_EXHAUSTED_MESSAGE, @@ -39,6 +47,46 @@ import { processDocumentAsync } from '@/lib/knowledge/documents/service' const logger = createLogger('TriggerKnowledgeProcessing') export { resolveQuotaContinuationDelayMs } +/** + * Ordinary failures keep the configured short retries. A transient database failure backs off for + * minutes, about an hour in total, so a slow database window does not exhaust every attempt inside + * it and leave an uploaded document failed for good. + */ +export const DOCUMENT_PROCESSING_RETRY_POLICY: BackgroundRetryPolicy = { + maxAttempts: envNumber(env.KB_CONFIG_MAX_ATTEMPTS, 3), + database: { maxAttempts: 6, baseDelayMs: 2 * 60 * 1000, maxDelayMs: 30 * 60 * 1000 }, +} + +/** + * A database failure whose next attempt is already scheduled, and recorded on the document as + * `pending` until {@link retryAt}. The message names only the database code; the driver error + * stays in `cause`, which the task runner does not record. + */ +export class DocumentProcessingDatabaseRetryError extends Error { + constructor( + message: string, + readonly retryAt: Date, + options: { cause: unknown } + ) { + super(message, options) + this.name = 'DocumentProcessingDatabaseRetryError' + } +} + +/** The `catchError` decision for `knowledge-process-document` after `attempt` (1-based) failed. */ +export function getDocumentProcessingRetry( + error: unknown, + attempt: number +): BackgroundRetryDecision { + const scheduled = findCause( + error, + (value): value is DocumentProcessingDatabaseRetryError => + value instanceof DocumentProcessingDatabaseRetryError + ) + if (scheduled) return { retryAt: scheduled.retryAt } + return getBackgroundRetryDecision(error, attempt, DOCUMENT_PROCESSING_RETRY_POLICY) +} + export async function runDocumentProcessing( rawPayload: DocumentProcessingPayload, attemptNumber = 1 @@ -56,6 +104,8 @@ export async function runDocumentProcessing( payload.processingSliceCount === undefined logger.info(`[${requestId}] Starting Trigger.dev processing for document: ${docData.filename}`) + /** Set from the service's callback, so control-flow narrowing cannot see it change. */ + let databaseRetryAt = null as Date | null try { const result = await processDocumentAsync( @@ -87,6 +137,14 @@ export async function runDocumentProcessing( : { quotaContinuationExhausted: true }), scheduleProviderContinuation: (error) => scheduleDocumentProcessingProviderContinuation(payload, error, true, chargedAtDispatch), + scheduleDatabaseRetry: (error) => { + databaseRetryAt = getDatabaseRetryAt( + error, + attemptNumber, + DOCUMENT_PROCESSING_RETRY_POLICY + ) + return databaseRetryAt + }, } ) @@ -100,6 +158,20 @@ export async function runDocumentProcessing( processingTime: Date.now() - startedAt, } } catch (error) { + if (databaseRetryAt) { + const diagnostic = getConnectorFailureDiagnostic(error) + logger.warn(`[${requestId}] Document processing will retry after a database failure`, { + documentId, + diagnostic, + attempt: attemptNumber, + retryAt: databaseRetryAt.toISOString(), + }) + throw new DocumentProcessingDatabaseRetryError( + diagnostic?.message ?? 'Database request failed.', + databaseRetryAt, + { cause: error } + ) + } const providerDeferral = getProviderCapacityDeferral(error) if (providerDeferral || error instanceof ProviderCapacityContinuationExhaustedError) { const outcome = @@ -205,7 +277,8 @@ export async function runDocumentProcessing( `[${requestId}] Failed to process document: ${docData.filename}`, diagnostic ?? error ) - if (diagnostic?.category === 'database') throw new Error(diagnostic.message) + /** Trigger records the thrown message and stack, never `cause`; Drizzle's message carries SQL. */ + if (diagnostic?.category === 'database') throw new Error(diagnostic.message, { cause: error }) throw error } } @@ -253,7 +326,13 @@ export const processDocument = task({ */ machine: 'medium-2x', retry: { - maxAttempts: envNumber(env.KB_CONFIG_MAX_ATTEMPTS, 3), + /** + * The ceiling for thrown errors: database retries use all of it, and + * `catchError` stops every other thrown error at `KB_CONFIG_MAX_ATTEMPTS`. + * A crashed or timed-out run is not retried; an out-of-memory kill is + * retried once, on the `outOfMemory` machine below. + */ + maxAttempts: backgroundRetryAttemptCeiling(DOCUMENT_PROCESSING_RETRY_POLICY), factor: envNumber(env.KB_CONFIG_RETRY_FACTOR, 2), minTimeoutInMs: envNumber(env.KB_CONFIG_MIN_TIMEOUT, 1000), maxTimeoutInMs: envNumber(env.KB_CONFIG_MAX_TIMEOUT, 10000), @@ -272,4 +351,5 @@ export const processDocument = task({ queue: interactiveProcessingQueue, run: (payload: DocumentProcessingPayload, { ctx }) => runDocumentProcessing(payload, ctx.attempt.number), + catchError: async ({ error, ctx }) => getDocumentProcessingRetry(error, ctx.attempt.number), }) diff --git a/apps/sim/background/knowledge-projection.ts b/apps/sim/background/knowledge-projection.ts new file mode 100644 index 00000000000..d3f52a23f6e --- /dev/null +++ b/apps/sim/background/knowledge-projection.ts @@ -0,0 +1,45 @@ +import { task } from '@trigger.dev/sdk' +import { + type BackgroundRetryPolicy, + backgroundRetryAttemptCeiling, + getBackgroundRetryDecision, +} from '@/lib/core/errors/background-retry' +import { + KNOWLEDGE_PROJECTION_PASS_BUDGET_MS, + KNOWLEDGE_PROJECTION_TASK_ID, + requestKnowledgeProjection, +} from '@/lib/knowledge/projection/enqueue' +import { runKnowledgeProjectionPass } from '@/lib/knowledge/projection/run' + +/** + * A pass gives a single document up on a lock or statement timeout without failing, so a failed + * pass lost its connection or its database. Those back off for minutes; the sweep starts a fresh + * pass every minute regardless, so a few attempts are enough. + */ +export const KNOWLEDGE_PROJECTION_RETRY_POLICY: BackgroundRetryPolicy = { + maxAttempts: 2, + database: { maxAttempts: 3, baseDelayMs: 60 * 1000, maxDelayMs: 5 * 60 * 1000 }, +} + +/** + * Runs one knowledge projector pass. One pass runs at a time and projects several documents at + * once itself; the prompt requests and the sweep collapse into whichever pass is queued. A pass + * that ran out of budget with marks left asks for the next one. Retry-safe: a pass writes only rows + * that differ from their source and removes a mark only on the generation it read. + */ +export const knowledgeProjectionTask = task({ + id: KNOWLEDGE_PROJECTION_TASK_ID, + machine: 'small-1x', + maxDuration: 15 * 60, + retry: { maxAttempts: backgroundRetryAttemptCeiling(KNOWLEDGE_PROJECTION_RETRY_POLICY) }, + queue: { name: KNOWLEDGE_PROJECTION_TASK_ID, concurrencyLimit: 1 }, + catchError: async ({ error, ctx }) => + getBackgroundRetryDecision(error, ctx.attempt.number, KNOWLEDGE_PROJECTION_RETRY_POLICY), + run: async () => { + const result = await runKnowledgeProjectionPass({ + budgetMs: KNOWLEDGE_PROJECTION_PASS_BUDGET_MS, + }) + if (result.remaining) await requestKnowledgeProjection() + return result + }, +}) diff --git a/apps/sim/background/projection-source-acl-backfill.ts b/apps/sim/background/projection-source-acl-backfill.ts deleted file mode 100644 index 71c80c86369..00000000000 --- a/apps/sim/background/projection-source-acl-backfill.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { task, tasks } from '@trigger.dev/sdk' -import { resolveTriggerRegion } from '@/lib/core/async-jobs/region' -import { - PROJECTION_SOURCE_ACL_BACKFILL_SHARDS, - PROJECTION_SOURCE_ACL_BACKFILL_TASK_ID, - type ProjectionSourceAclBackfillPayload, - projectionSourceAclChainTag, - runProjectionSourceAclBackfill, -} from '@/lib/knowledge/search/projection-source-acl-backfill' - -/** One run's share of the backfill, inside the worker's run ceiling with room to end its page. */ -const RUN_BUDGET_MS = 60 * 60 * 1000 - -/** - * Trigger.dev wrapper around `runProjectionSourceAclBackfill`. A run fills unset rows for up to - * {@link RUN_BUDGET_MS}, then triggers its continuation from the cursor it reached, so the whole - * projection is filled across as many bounded runs as it takes. Retry-safe: every run writes only - * rows still unset, so a retried or restarted run repeats no write. A shard's continuation keeps - * its shard, so a sliced fill stays sliced until every slice is done. - */ -export const projectionSourceAclBackfillTask = task({ - id: PROJECTION_SOURCE_ACL_BACKFILL_TASK_ID, - machine: 'small-1x', - retry: { maxAttempts: 3 }, - /** - * One run per shard the id space may be sliced into. Shards fill disjoint ranges, so runs never - * fill the same page against each other; an unsliced chain still runs one at a time because each - * run triggers its continuation only as it ends. - */ - queue: { - name: PROJECTION_SOURCE_ACL_BACKFILL_TASK_ID, - concurrencyLimit: PROJECTION_SOURCE_ACL_BACKFILL_SHARDS, - }, - run: async (payload: ProjectionSourceAclBackfillPayload) => { - const cursor = await runProjectionSourceAclBackfill(payload, { budgetMs: RUN_BUDGET_MS }) - if (!cursor) return - const continuation: ProjectionSourceAclBackfillPayload = { ...payload, cursor } - await tasks.trigger(PROJECTION_SOURCE_ACL_BACKFILL_TASK_ID, continuation, { - region: await resolveTriggerRegion(), - /** The chain's tag rides on every continuation, so a start finds the chain wherever it is. */ - tags: [projectionSourceAclChainTag(payload.shard)], - }) - }, -}) diff --git a/apps/sim/background/workspace-file-search-index.test.ts b/apps/sim/background/workspace-file-search-index.test.ts index b261a0e3b7f..1da3dc564b6 100644 --- a/apps/sim/background/workspace-file-search-index.test.ts +++ b/apps/sim/background/workspace-file-search-index.test.ts @@ -5,6 +5,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' const mocks = vi.hoisted(() => ({ indexWorkspaceFile: vi.fn(), + retry: vi.fn(), markFailed: vi.fn(), task: vi.fn((config: unknown) => config), })) @@ -12,10 +13,12 @@ const mocks = vi.hoisted(() => ({ vi.mock('@trigger.dev/sdk', () => ({ task: mocks.task })) vi.mock('@/lib/workspace-files/search/indexing', () => ({ indexWorkspaceFileForSearch: mocks.indexWorkspaceFile, + getWorkspaceFileSearchRetry: mocks.retry, markWorkspaceFileSearchIndexFailed: mocks.markFailed, })) import { + FILE_SEARCH_INDEX_CAPACITY_MAX_ATTEMPTS, FILE_SEARCH_INDEX_GLOBAL_CONCURRENCY, FILE_SEARCH_INDEX_MAX_DURATION_SECONDS, } from '@/lib/workspace-files/search/constants' @@ -37,7 +40,7 @@ describe('workspace file search index task', () => { id: 'workspace-file-search-index', machine: 'medium-2x', maxDuration: FILE_SEARCH_INDEX_MAX_DURATION_SECONDS, - retry: { maxAttempts: 3 }, + retry: { maxAttempts: FILE_SEARCH_INDEX_CAPACITY_MAX_ATTEMPTS }, queue: { name: 'workspace-file-search-index', concurrencyLimit: FILE_SEARCH_INDEX_GLOBAL_CONCURRENCY, @@ -65,4 +68,15 @@ describe('workspace file search index task', () => { await workspaceFileSearchIndexTask.onFailure({ payload }) expect(mocks.markFailed).toHaveBeenCalledWith(payload) }) + + it('lets the indexing retry policy schedule each failed attempt', async () => { + const error = new Error('statement timeout') + const decision = { retryAt: new Date('2026-08-29T12:02:00.000Z') } + mocks.retry.mockReturnValue(decision) + + await expect( + workspaceFileSearchIndexTask.catchError({ error, ctx: { attempt: { number: 2 } } }) + ).resolves.toBe(decision) + expect(mocks.retry).toHaveBeenCalledWith(error, 2) + }) }) diff --git a/apps/sim/background/workspace-file-search-index.ts b/apps/sim/background/workspace-file-search-index.ts index c44bd47acf3..86340f6ae01 100644 --- a/apps/sim/background/workspace-file-search-index.ts +++ b/apps/sim/background/workspace-file-search-index.ts @@ -1,9 +1,11 @@ import { task } from '@trigger.dev/sdk' import { + FILE_SEARCH_INDEX_CAPACITY_MAX_ATTEMPTS, FILE_SEARCH_INDEX_GLOBAL_CONCURRENCY, FILE_SEARCH_INDEX_MAX_DURATION_SECONDS, } from '@/lib/workspace-files/search/constants' import { + getWorkspaceFileSearchRetry, indexWorkspaceFileForSearch, markWorkspaceFileSearchIndexFailed, type WorkspaceFileSearchIndexPayload, @@ -17,13 +19,15 @@ export const workspaceFileSearchIndexTask = task({ id: 'workspace-file-search-index', machine: 'medium-2x', maxDuration: FILE_SEARCH_INDEX_MAX_DURATION_SECONDS, - retry: { maxAttempts: 3 }, + /** The ceiling for capacity retries; `catchError` stops other failures sooner. */ + retry: { maxAttempts: FILE_SEARCH_INDEX_CAPACITY_MAX_ATTEMPTS }, queue: { name: 'workspace-file-search-index', concurrencyLimit: FILE_SEARCH_INDEX_GLOBAL_CONCURRENCY, }, run: (payload: WorkspaceFileSearchIndexPayload, { signal }) => indexWorkspaceFileForSearch(payload, signal), + catchError: async ({ error, ctx }) => getWorkspaceFileSearchRetry(error, ctx.attempt.number), onFailure: async ({ payload }) => { await markWorkspaceFileSearchIndexFailed(payload) }, diff --git a/apps/sim/content/library/best-ai-agent-builders-for-human-approval-workflows/index.mdx b/apps/sim/content/library/best-ai-agent-builders-for-human-approval-workflows/index.mdx new file mode 100644 index 00000000000..99aa410a47d --- /dev/null +++ b/apps/sim/content/library/best-ai-agent-builders-for-human-approval-workflows/index.mdx @@ -0,0 +1,153 @@ +--- +slug: best-ai-agent-builders-for-human-approval-workflows +title: 'Best AI Agent Builders for Human Approval Workflows' +description: 'Compare AI agent builders for human-in-the-loop approval workflows, including Sim, n8n, Zapier, Workato, Make, Dify.ai, Relevance AI, and Gumloop.' +date: 2026-09-23 +updated: 2026-09-23 +authors: + - andrew +readingTime: 13 +tags: [AI Agents, Human in the Loop, Workflow Automation, Agent Builders, Sim] +ogImage: /library/best-ai-agent-builders-for-human-approval-workflows/cover.jpg +canonical: https://www.sim.ai/library/best-ai-agent-builders-for-human-approval-workflows +draft: false +faq: + - q: "How does a timeout-based pause differ from an indefinite pause?" + a: "A timeout-based pause resumes, fails, or expires after a set period. An indefinite pause keeps the workflow suspended until a person or external system responds, which better suits approvals with unpredictable response times." + - q: "Does webhook resume require custom code?" + a: "A builder can provide a native resume webhook without requiring code inside the workflow. You may still need configuration or custom code in the external form, application, or service that sends the approval response." + - q: "What counts as an audit log rather than a run log?" + a: "A run log records workflow execution, including completed blocks, outputs, and errors. An audit log usually identifies who acted and when, and it may also record administrative changes or enforce retention controls needed for governance. Sim provides block-by-block run logs, but buyers should confirm whether those records meet their compliance requirements." + - q: "How do guardrails differ from approval steps?" + a: "Guardrails automatically check inputs or outputs against defined rules and can block unsafe or invalid activity. Approval steps pause execution and ask a person to make a decision. Sim provides separate Guardrails and Human in the Loop blocks for these roles." +--- + +## TL;DR + +- [Sim provides a native Human in the Loop block](https://docs.sim.ai/workflows/blocks/human-in-the-loop) with an indefinite pause, multi-channel notifications, and resume options through its portal, REST API, or webhook. +- [Zapier](https://help.zapier.com/hc/en-us/articles/38731463206029-Request-approval-to-keep-your-workflow-running-with-Human-in-the-Loop) and [Workato](https://docs.workato.com/en/workflow-apps/workflow-apps-connector/assign-task-to-users-action.html) offer native approval patterns suited to existing automations and enterprise workflows. [n8n can pause and resume through webhooks or forms](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.wait), but a tailored approval interface usually requires additional work. +- Dify.ai, Relevance AI, and Gumloop now document native human-input or approval features for specific product surfaces. Make still requires buyers to assemble the approval cycle from scenarios, webhooks, connected apps, and state storage. +- Compare builders by pause duration, approval channels, resume paths, run or audit records, guardrails, and deployment controls. Sim fits buyers who want an extensible workspace for custom multi-model agents. + +## What "human in the loop" means in an agent builder + +Human-in-the-loop support lets a workflow stop at a defined checkpoint and wait for a person to approve, reject, or edit its proposed action. A complete implementation preserves the workflow state, records the decision, and resumes execution from the checkpoint without restarting earlier steps. + +Pause behavior determines whether the workflow can support long-running approvals. An indefinite pause keeps the run available until a person responds. A timeout-based step may expire, fail, or follow a default branch after a set period. Polling-based patterns repeatedly check an external source for a decision, which can add delays and require extra workflow logic. + +Native approval blocks manage the pause and decision inside the builder. Integration-based approaches may send data to an external form or messaging app, then use a webhook or scheduled check to restart the workflow. Both approaches can work, but external components add configuration and create more places to manage credentials, state, and errors. + +Start by checking which channels can deliver approval requests, such as email, Slack, Microsoft Teams, SMS, or a web portal. Then verify whether a reviewer can resume the same run through the builder, an API, or a webhook. + +Review and governance features also affect the choice. Run logs should show each workflow step, while an approval record should identify the decision, submission time, and response. Guardrails should restrict unsafe inputs or actions before execution. Deployment controls should govern who can edit, publish, and run the workflow. Together, these records and controls show whether the builder manages the full approval cycle or merely sends a notification that custom automation must process. For a broader governance framework, see how to [govern AI agents across teams and enterprise workspaces](https://www.sim.ai/library/govern-ai-agents-multiple-teams-enterprise-workspace). + +## How we evaluated these tools + +This guide evaluates approval steps, pause behavior, resume methods, notification channels, run records, guardrails, and deployment controls. Each product section distinguishes confirmed capabilities from features that buyers still need to verify. We treat a capability as native only when the vendor documents it as part of the product. + +A marketing claim or technically possible integration does not prove that a product provides a native approval feature. When documentation supports a workflow only through custom code, external forms, webhooks, or third-party connectors, we label that requirement explicitly. When documentation does not confirm a capability, we mark it as unverified rather than assuming the product lacks it. + +Each assessment considers the work required to configure, operate, and review an approval workflow. A product may support an approval workflow, but the setup effort, available resume paths, and quality of execution records can differ substantially. These criteria complement a wider [AI workflow automation platform buyer's checklist](https://www.sim.ai/library/ai-workflow-automation-platform-buyers-checklist). + +## Comparison table: human-in-the-loop capabilities + +“Custom or integration” means the approval cycle requires external components or custom logic. “Verify” means the available evidence does not establish whether the capability is native. + +| Tool | Pause behavior | Approval channels | Resume method | Audit record | Guardrails | Deployment control | Status | +| --- | --- | --- | --- | --- | --- | --- | --- | +| [Sim](https://docs.sim.ai/workflows/blocks/human-in-the-loop) | Indefinite by default | Slack, Gmail, Teams, SMS, webhook | Portal, REST API, webhook | Block-level run logs | Native block | Workspace controls | Native | +| [n8n](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.wait) | Wait node pauses execution | Forms or messaging integrations | Webhook or form submission | Execution history | Custom logic | Self-hosted or cloud | Native pause, configurable approval experience | +| [Zapier](https://help.zapier.com/hc/en-us/articles/38731463206029-Request-approval-to-keep-your-workflow-running-with-Human-in-the-Loop) | Human in the Loop pauses a Zap | Email, Slack, or another Zap | Approval response | Zap run history | Filters and custom checks | Managed cloud | Native approval with integrations | +| [Workato](https://docs.workato.com/en/workflow-apps/workflow-apps-connector/assign-task-to-users-action.html) | Task pauses until completion or expiry | Email and Workflow Apps portal | Portal or programmatic action | Request activity history | Policies and recipe logic | Enterprise environments | Native task approval | +| [Gumloop](https://docs.gumloop.com/core-concepts/human_in_the_loop) | Agent pauses before protected tools | In-app, Slack DM, or chat | Approval response | Agent interaction context | Tool approvals and app rules | Managed cloud | Native agent tool approval | +| [Make](https://help.make.com/webhooks) | Scenarios usually split around approval | Email, forms, or connected apps | Webhook-triggered scenario | Scenario history | Filters and custom logic | Managed cloud | Custom or integration | +| [Relevance AI](https://relevanceai.com/docs/build/workforces/workforce-features/approvals-and-escalations) | Workforce task waits for approval | Workforce Task View and alerts | Task approval | Task history | Approval modes | Managed platform | Native workforce approval | +| [Dify.ai](https://docs.dify.ai/en/cloud/use-dify/nodes/human-input) | Human Input node pauses a workflow | Web app form or email delivery | Form submission or API flow | Workflow run history | Model and workflow controls | Cloud or self-hosted | Native human input | + +## Sim + +[Sim](https://sim.ai) suits buyers who need an extensible workspace for custom AI agents with human checkpoints. Its workflow builder provides separate [Human in the Loop](https://docs.sim.ai/workflows/blocks/human-in-the-loop), [Guardrails](https://docs.sim.ai/workflows/blocks/guardrails), [Evaluator](https://docs.sim.ai/workflows/blocks/evaluator), and [Wait](https://docs.sim.ai/workflows/blocks/wait) blocks. You can keep approval logic distinct from model evaluation, safety checks, and ordinary workflow delays. + +The Human in the Loop block pauses a workflow indefinitely by default, with no preset timeout. Sim can notify reviewers through Slack, Gmail, Microsoft Teams, SMS, or a webhook. A reviewer can resume the workflow through the Sim portal, REST API, or webhook, which supports both built-in review and custom approval interfaces. + +Each Sim approval step exposes `url`, `resumeEndpoint`, `response`, `submission`, and `submittedAt` outputs. The `submission` and `submittedAt` outputs can carry the reviewer’s response and its timestamp into later blocks. An external application can call `resumeEndpoint` to continue the paused run. Block-by-block run logs then support post-run review of the workflow’s execution. Buyers with formal compliance requirements should confirm whether those run logs meet their retention, access-control, and immutability policies. The distinction between execution visibility and formal audit evidence is covered in more depth in this guide to [AI agent observability](https://www.sim.ai/library/ai-agent-observability). + +Sim fits buyers building multi-model agents that need tailored access to tools and data. The separate control blocks let you place review gates around specific actions instead of routing every agent response through the same approval path. Sim may require more workflow design than a simpler no-code automation tool, so buyers who only need one approval step inside an existing app workflow may prefer a lighter option. + +## n8n + +[n8n supports approval gates by pausing a workflow with its Wait node](https://docs.n8n.io/integrations/builtin/core-nodes/n8n-nodes-base.wait). A webhook request or form submission can resume the same execution and pass the reviewer’s decision back into the workflow. You can then route approved and rejected items through separate branches. + +The Wait node supplies the pause and resume mechanism. A tailored approval interface requires additional components. Depending on the n8n design, the approval request can use an n8n form or connect the resume webhook to a messaging tool or custom application where the reviewer can inspect the request and respond. More specialized interfaces may require custom nodes or code. + +[n8n can be self-hosted](https://docs.n8n.io/deploy/host-n8n), giving operators control over workflow logic, credentials, and execution data. However, a recorded workflow execution should not automatically be treated as a dedicated approval audit trail. You may need to store the reviewer’s identity, decision, timestamp, and comments explicitly, and [execution-data retention is configurable](https://docs.n8n.io/deploy/host-n8n/configure-n8n/scaling/manage-execution-data). + +n8n best suits technical users who want self-hosted control and can build the interface around the approval step. Buyers seeking a ready-made reviewer portal with native notification channels may prefer a builder that packages those features with its pause block. + +## Dify.ai + +[Dify.ai provides a Human Input node](https://docs.dify.ai/en/cloud/use-dify/nodes/human-input) that pauses Workflow and Chatflow applications, presents a customizable form, and routes the workflow according to the recipient’s response. This is a native approval path rather than merely a canvas for producing content to review elsewhere. + +For application integrations, Dify documents an [API flow for a paused Human Input form](https://docs.dify.ai/en/api-reference/guides/human-input-flow). A streaming run emits a `human_input_required` event with a form token; submitting the form resumes the workflow. Buyers should verify timeout settings, delivery options, and state-retention behavior for their selected cloud or self-hosted version. + +[Dify records workflow Run History](https://docs.dify.ai/en/self-host/use-dify/debug/history-and-logs), including application-level results and node-level tracing in the product interface. Buyers with long-running approvals or compliance requirements should still verify reviewer identity, retention, access controls, and the audit properties of these records before choosing it. + +Dify.ai may fit buyers building conversational or RAG applications that also need review points in the core workflow. Teams embedding review in another application should test the streaming API lifecycle and confirm that their interface handles retries, expired forms, and duplicate submissions safely. + +## Relevance AI + +[Relevance AI Workforces support approvals and escalations](https://relevanceai.com/docs/build/workforces/workforce-features/approvals-and-escalations) for controlling when agents may take actions. Buyers can configure approval modes, monitor requests through the Workforce Task View, and set alerts for work requiring human attention. + +For each proposed checkpoint, verify the pause duration, decision record, and method used to continue the same task. A native approval step should pause the task, record the decision, and resume the correct work without polling. Relevance AI’s [Tasks page groups work waiting for approval](https://relevanceai.com/docs/build/agents/give-your-agent-tasks/tasks-page) alongside escalations and errors, while each Task represents a run by an Agent or Workforce. + +Relevance AI makes the most sense when agent coordination drives the purchase and human review supports that model. Buyers who need long-running approvals, formal audit records, or external API and webhook resume methods should confirm those exact capabilities through current documentation or a product demonstration before choosing it. + +## Gumloop + +[Gumloop documents Human in the Loop controls for agents](https://docs.gumloop.com/core-concepts/human_in_the_loop). When an agent attempts a tool action configured to require approval, it pauses and shows the proposed tool, arguments, and intent. Approval requests can arrive through an in-app notification, Slack DM, or the chat thread. + +This capability is aimed at agent tool approval rather than proving that every visual workflow can suspend indefinitely at an arbitrary node. Buyers should verify the pause duration, reviewer identity, record retention, and whether their selected workflow or agent surface can be resumed through the API method their application requires. + +Gumloop may fit less technical users who want visual AI automation with approval around sensitive agent actions. Workflows that require arbitrary external resume endpoints, a dedicated approval portal, or formal audit records should be tested against the intended deployment before purchase. + +## Zapier + +[Zapier’s Human in the Loop tool](https://help.zapier.com/hc/en-us/articles/38731463206029-Request-approval-to-keep-your-workflow-running-with-Human-in-the-Loop) adds review gates to Zaps. Its Request Approval action pauses a Zap run so one or more reviewers can approve, decline, or change submitted data before later actions continue. Notifications can go to email, Slack, or another Zap that connects to an app in Zapier’s ecosystem. + +[Delay by Zapier serves a different purpose](https://help.zapier.com/hc/en-us/articles/8496288754829-Add-delays-to-Zap-workflows). A delay resumes the Zap at a scheduled time or after a set interval, so it cannot represent an open-ended approval by itself. Buyers should confirm the current execution limits and timeout behavior for Human in the Loop steps rather than assume that Zapier will preserve every paused run indefinitely. + +Zapier works best when approval forms one step inside an existing app automation. For example, a sales team can review AI-generated outreach before Zapier sends it through an email platform. Workflows that require long-running pauses, several resume methods, or specialized approval interfaces may need extra Zaps, webhooks, or another application. Zapier fits buyers who already use its app ecosystem and want an approval gate inside an existing Zap rather than a dedicated agent workspace. + +## Make + +Make handles approval workflows through visual scenarios, but buyers usually assemble the approval checkpoint from several modules. A Make scenario can send a request through a connected app, and [a custom webhook can receive the reviewer’s response and trigger a scenario](https://help.make.com/webhooks). A timed Sleep module does not by itself provide an open-ended human approval. + +Buyers should verify whether their design continues the original business process at the approval step because an incoming webhook triggers scenario execution. You may need [a data store to transfer state between scenario runs](https://help.make.com/l6du-data-stores), a second scenario, or custom logic to correlate the response and continue processing. Make’s [scenario history records run and change-log details](https://help.make.com/scenario-history), but teams should decide whether those records satisfy their approval-audit requirements. + +Make fits operations teams that want a visual builder and can manage moderate technical customization. Buyers who need indefinite suspension, a dedicated approval portal, or a single native audit trail should confirm those requirements against their planned scenario design. + +## Workato + +[Workato Workflow Apps can assign a task that pauses a recipe job](https://docs.workato.com/en/workflow-apps/workflow-apps-connector/assign-task-to-users-action.html) until the task is completed or expires. The action can email assignees, who can complete the task in the Workflow Apps portal, and Workato also documents a programmatic completion action. Buyers should configure and test the expiry behavior for each design rather than assume that tasks remain open indefinitely. + +Workato documents [activity audit logs](https://docs.workato.com/features/activity-audit-log-view.html), [role-based environment access](https://docs.workato.com/en/user-accounts-and-teams/role-based-access/managing-environment-access.html), and [SAML single sign-on for Workflow Apps](https://docs.workato.com/en/workflow-apps/saml). Buyers should confirm which features are enabled in their edition and deployment. Development, testing, and production environments can help administrators control access across the automation lifecycle, while [Workflow Apps activity history](https://docs.workato.com/en/workflow-apps/workflow-apps-connector/get-activity-history-action.html) can capture task assignments, status changes, comments, and the person performing an activity. + +Workato suits larger organizations that already manage integrations through a central IT or operations function. Its approval workflows may require more configuration than a dedicated human-in-the-loop block. Buyers should verify whether each notification channel and external resume path works natively or requires a connector, recipe, or custom interface. + +## Choosing the right builder for your approval workflow + +Choose [Sim](https://sim.ai) when approvals may remain open indefinitely and reviewers need several notification options. Its dedicated Human in the Loop block can notify through Slack, Gmail, Microsoft Teams, SMS, or webhooks. Reviewers can resume a run through the Sim portal, REST API, or webhook. Sim fits custom agents that also need tailored model, tool, and data access. For a wider comparison of platform architecture and deployment choices, review the [best AI agent builders](https://www.sim.ai/library/best-ai-agent-builder-2026). + +Choose n8n when self-hosting and control over workflow logic take priority. Its wait and webhook patterns suit technical users who can build or connect the approval interface. Make offers similar flexibility for visual automation, but approval experiences may depend on webhooks, forms, state storage, or other configured components. + +Choose Workato when enterprise governance drives the purchase. Buyers with strict requirements for access control, deployment management, and auditability may value those controls more than a simple approval node. Workato also makes more sense when your company already uses its integration and governance environment. + +Consider Zapier for an approval step inside an existing app automation. Consider Gumloop for approval before sensitive agent tool calls. Dify.ai may suit an LLM application with form-based human input, while Relevance AI may suit multi-agent coordination with task approvals. In every case, buyers should verify how an approval gate pauses and resumes the underlying run or task. + +Native approval capability can reduce implementation work for regulated decisions and long-running approvals because one product can retain the paused run and reviewer response. Buyers should still verify whether the product records reviewer identity, the resume event, and later execution in a suitable audit trail. Integration-based approvals can work well for short-lived internal tasks, but you must account for expired runs, duplicate webhook calls, authentication, and records split across several tools. + +## Conclusion + +Choose an AI agent builder based on whether it can preserve a paused run, capture the reviewer’s decision, and resume the correct execution path through the channels your workflow requires. Some tools provide dedicated pause, notification, and resume controls, while others assemble approvals through webhooks, forms, or external automation. Buyers should verify pause duration and resume paths first, then confirm that approval records and access controls meet their governance requirements. + +[Sim](https://sim.ai) suits buyers building custom multi-model agents with tailored access to tools and data. Buyers who only need a simple approval gate inside existing automations may prefer a lightweight add-on with less setup. diff --git a/apps/sim/content/library/best-ai-agents-for-data-extraction-and-rag-in-2026/index.mdx b/apps/sim/content/library/best-ai-agents-for-data-extraction-and-rag-in-2026/index.mdx index dc0c47f0125..23d45238ce5 100644 --- a/apps/sim/content/library/best-ai-agents-for-data-extraction-and-rag-in-2026/index.mdx +++ b/apps/sim/content/library/best-ai-agents-for-data-extraction-and-rag-in-2026/index.mdx @@ -1,152 +1,253 @@ --- slug: best-ai-agents-for-data-extraction-and-rag-in-2026 title: 'Best AI Agents for Data Extraction and RAG in 2026' -description: Compare the best AI agents for document extraction, SQL queries, spreadsheet analysis, and RAG over internal documents in 2026. See how Sim, n8n, Zapier, Make, and Gumloop handle data workflows. +description: 'Compare the best AI agents for data extraction and RAG in 2026, including Sim, n8n, Unstructured, and LlamaIndex for visual, automation, document, and code-first workflows.' date: 2026-07-01 -updated: 2026-07-23 +updated: 2026-09-23 authors: - andrew -readingTime: 19 +readingTime: 12 tags: [AI Agents, Data Extraction, RAG, Sim] ogImage: /library/best-ai-agents-for-data-extraction-and-rag-in-2026/cover.jpg canonical: https://www.sim.ai/library/best-ai-agents-for-data-extraction-and-rag-in-2026 draft: false faq: - - q: "Do AI agents need a separate vector database for RAG?" - a: "Not always. Sim includes native Knowledge Bases that handle chunking, embedding, storage, and semantic retrieval, so you skip standing up a separate vector database like Pinecone or Weaviate. You need an external vector store only when a platform lacks native retrieval and forces you to wire one in yourself." - - q: "Can I use AI agents to extract tables from PDFs into a database?" - a: "Yes. Sim parses documents through its Files handling, including OCR for scanned pages, and lands the results in Tables as structured rows you can push to a database. Platforms without native parsing require a third-party OCR step, and those steps often break on inconsistent table layouts." - - q: "What's the difference between an AI agent and a RAG pipeline?" - a: "A RAG pipeline retrieves relevant documents and feeds them to a model to ground its answers. An AI agent wraps that retrieval inside a larger workflow, calling tools, querying data, and taking actions across steps. In Sim, the Knowledge Base supplies the retrieval, and the agent decides when to use it." - - q: "What file types and sizes can Sim process?" - a: "Sim accepts PDF, Word, TXT, Markdown, HTML, Excel, PowerPoint, CSV, JSON, and YAML at up to 100 MB per file, with best performance under 50 MB. Scanned PDFs are supported through Azure or Mistral OCR." - - q: "Do these platforms support self-hosted or on-prem data for compliance?" - a: "n8n and Sim both offer self-hosting, which keeps sensitive documents inside your own infrastructure for regulatory requirements. Sim self-hosts under Apache 2.0 via Docker or Kubernetes, while n8n uses a fair-code license with commercial-use restrictions. Zapier, Make, and Gumloop run as managed cloud services, so your data passes through their systems. Check each vendor's current deployment options before committing to a compliance-sensitive workflow." + - q: "What is the best AI agent for data extraction and RAG?" + a: "Sim is the best overall AI agent platform for teams that want to combine document extraction, retrieval, model calls, tools, and human review in one visual workflow. The right choice still depends on document complexity, deployment requirements, existing systems, and whether the team prefers visual or code-first development." + - q: "What is the difference between data extraction, retrieval, RAG, and agentic RAG?" + a: "Data extraction converts source content into structured information, retrieval finds relevant information, RAG gives retrieved context to a model, and agentic RAG lets an agent choose and repeat those operations while pursuing a goal. These stages should be evaluated separately because a fluent final answer can conceal extraction or retrieval failures." + - q: "How should I evaluate an AI agent for data extraction and RAG?" + a: "Sim recommends evaluating extraction fidelity, retrieval quality, answer groundedness, workflow reliability, observability, deployment control, licensing, latency, and total cost on a representative test set. A platform should not be selected from a polished demonstration alone." + - q: "Which AI agent is best for extracting data from PDFs?" + a: "Sim is the best fit when PDF extraction must feed a visual agentic workflow, while Unstructured is a stronger specialist candidate when document partitioning and preprocessing are the main problem. Scanned files, tables, forms, multi-column layouts, and handwriting should be tested separately." + - q: "Which AI agent is best for code-first RAG development?" + a: "LlamaIndex is the strongest choice in this comparison for developers who want code-first control over ingestion, indexing, retrieval, and response synthesis. Sim is better suited to teams that want those stages represented as a visual, inspectable workflow." + - q: "Is n8n good for RAG workflows?" + a: "n8n is a strong choice for RAG workflows that must connect to a broad business automation estate. n8n should be tested carefully when retrieval evaluation, document-specific processing, or complex agent state is central to the application." + - q: "Is Sim open source?" + a: "Sim is open source under the OSI-approved Apache License 2.0 and supports free self-hosting. Teams should verify the current terms of any hosted Sim plan separately because hosted product pricing can change." + - q: "Is n8n open source?" + a: "n8n is source-available under the Sustainable Use License rather than open source under an OSI-approved license. The license permits many internal and self-hosted uses but includes restrictions that teams should review before commercial redistribution or offering hosted n8n to third parties." + - q: "Sim vs n8n: which is better for data extraction and RAG?" + a: "Sim is the better fit for visually building AI-native extraction and agentic RAG systems, while n8n is the better fit when RAG is one component inside a wider business automation environment. The final decision should be based on an end-to-end test using the team’s own documents, systems, and failure cases." + - q: "Sim vs Gumloop: which is better for data extraction and RAG?" + a: "Sim is the better fit when open-source licensing, self-hosting, and explicit control over an agent workflow matter, while Gumloop may suit teams seeking a managed visual automation experience. Gumloop’s current pricing, deployment options, and product limits should be verified on Gumloop’s official pages before selection." + - q: "Can I self-host an AI agent for data extraction and RAG?" + a: "Sim and n8n can both be self-hosted, but Sim uses the OSI-approved Apache License 2.0 while n8n uses the source-available Sustainable Use License. Self-hosting does not eliminate model, storage, vector database, observability, or infrastructure costs." + - q: "Do I need a vector database for RAG?" + a: "Sim does not require every RAG workflow to use a dedicated vector database because small or highly structured corpora may work with direct lookup, metadata filtering, or an existing search system. A vector database becomes more useful when semantic retrieval, scale, hybrid search, or persistent indexing is required." + - q: "How do I test whether a RAG agent is accurate?" + a: "Sim recommends testing extraction accuracy, retrieval recall, citation precision, groundedness, abstention behavior, task success, latency, and cost as separate measurements. Teams should include adversarial, ambiguous, outdated, malformed, and no-answer examples in the evaluation set." + - q: "What is the best AI agent builder?" + a: "Sim is a leading AI agent builder, but the canonical Sim guide for that broad question is Best AI Agent Builder in 2026 at /library/best-ai-agent-builder-2026. This guide addresses the narrower problem of choosing a platform for data extraction and RAG." --- ## TL;DR -Sim leads across all four data and RAG use cases because it treats document parsing, structured tables, and semantic retrieval as native primitives rather than add-ons. Each competitor still wins in a specific scenario. +Sim is the best overall choice for teams that want to build visual data-extraction and agentic RAG workflows with explicit control over ingestion, retrieval, model calls, tools, branching, and human review. -- **PDF and document extraction:** Sim's [Files](https://docs.sim.ai/files) parsing into structured output is the default, with OCR for scanned pages and support for 10 file types up to 100 MB each. Choose Gumloop when you want pre-built scraping and enrichment nodes. -- **SQL in plain English:** Sim's [Tables](https://docs.sim.ai/tables) ground natural-language queries against schema. Choose n8n when you already run self-hosted database workflows. -- **Spreadsheet analysis:** Sim's Tables ingest CSV and TSV files with inferred column types, batch-inserting rows 5,000 at a time. Choose Make when you need granular, visible transformation steps. -- **RAG over internal documents:** Sim's [Knowledge Bases](https://docs.sim.ai/knowledgebase) with connector sync across 50+ sources keep retrieval fresh. Choose Zapier when RAG is a light task beside hundreds of app connections. +The closest alternatives solve different parts of the problem. [n8n](https://docs.n8n.io/build/integrate-ai) is strongest when RAG must sit inside a broad automation estate, [Unstructured](https://docs.unstructured.io/concepts/partitioning) specializes in turning difficult documents into usable elements, and [LlamaIndex](https://docs.llamaindex.ai/en/stable/examples/cookbooks/oreilly_course_cookbooks/) gives developers code-first control over RAG pipelines. -## What makes an AI agent good at data extraction and RAG? +The correct choice depends on where the system is most likely to fail. Teams processing irregular PDFs may need stronger preprocessing, teams integrating operational systems may prioritize automation breadth, and engineering teams may prefer a framework that exposes retrieval behavior directly in code. -Four mechanics separate an agent that actually handles data work from one that stitches together workarounds. The first is native document parsing, meaning the platform reads a PDF or spreadsheet and returns clean structured output without an external OCR service. The second is semantic retrieval backed by a vector store that stays synced to its source, so answers reflect the current document, not a snapshot from three weeks ago. The third is how the platform holds intermediate data. Structured tables let an agent query and transform rows, while flat file passing forces you to reparse the same blob at every step. The fourth is natural-language querying against a database schema, where the agent grounds a plain-English question in the actual columns instead of guessing. +## What are the best AI agents for data extraction and RAG in 2026? -Where a platform sits on these four mechanics depends on what it was built to do. Zapier, Make, and n8n started as integration platforms that move data between apps, so they treat documents and vectors as payloads to route rather than objects to reason over. You reach RAG through a bolt-on vector database node and reach extraction through a third-party parsing service, and you assemble the pipeline yourself. +Sim, n8n, Unstructured, and LlamaIndex are the strongest candidates in this guide because they represent four distinct approaches to production data extraction and RAG. -Agent-native platforms like Sim and Gumloop invert that assumption. Sim ships Knowledge Bases for semantic retrieval with connector sync, Tables for structured extraction pipelines, and Files for document handling, so the retrieval and storage layer lives inside the agent. Sim adds a fifth option the others do not have: [Mothership](https://www.sim.ai/blog/mothership), a natural-language control plane with full context over your workflows, tables, knowledge bases, and files. You describe the pipeline in plain English and Sim builds, tests, and deploys it. +| Rank | Platform | Best fit | Extraction approach | RAG approach | Workflow style | Main trade-off | +|---|---|---|---|---|---|---| +| 1 | [Sim](https://github.com/simstudioai/sim) | Visual, agentic extraction and RAG workflows | Connect extraction steps, models, APIs, code, and review logic | Explicit retrieval and generation steps inside an agent workflow | Visual builder with self-hosting available | Teams must still select and evaluate their own parsing, model, and retrieval components | +| 2 | [n8n](https://docs.n8n.io/build/integrate-ai) | RAG connected to wider business automation | Uses nodes, APIs, code, and external document services | AI and retrieval steps can be embedded in general automations | Visual workflow automation | Less specialized around document and retrieval evaluation than a dedicated RAG stack | +| 3 | [Unstructured](https://docs.unstructured.io/concepts/overview) | Complex document preprocessing and partitioning | Document-focused parsing and element extraction | Primarily prepares content for downstream retrieval systems | API, library, and pipeline components | Not a complete general-purpose agent builder by itself | +| 4 | [LlamaIndex](https://docs.llamaindex.ai/en/stable/examples/cookbooks/oreilly_course_cookbooks/) | Code-first RAG applications | Programmable readers, transformations, and ingestion pipelines | Deep control over indexing, retrieval, and synthesis | Developer framework | Requires more engineering ownership than a visual platform | -Judge each tool in the sections that follow against these mechanics, not against its marketing. +No ranking can replace a representative evaluation. The best platform is the one that produces reliable outputs on the team’s actual files, permissions, queries, integrations, and failure cases. -## What is the best AI agent for extracting data from PDFs and documents in 2026? +## What is the difference between data extraction, retrieval, RAG, and agentic RAG? -Sim extracts data from PDFs and documents with the least assembly because it handles the file, parses it, and returns structured output inside one agent. You upload a document through Files, point an agent at it, and pull specific fields into a Table without wiring an OCR service or a parsing API in between. Sim accepts PDF, Word, TXT, Markdown, HTML, Excel, PowerPoint, CSV, JSON, and YAML at up to 100 MB per file, and extracts text from scanned, image-based pages through Azure or Mistral OCR. The document goes in, structured data comes out, and no glue steps sit between the two. +Data extraction, retrieval, RAG, and agentic RAG are separate stages of a knowledge workflow, and treating them as synonyms makes failures harder to diagnose. -The four integration platforms take a longer route because none treats document parsing as a first-class capability. n8n gives you a PDF node for basic text extraction, but anything with tables, scanned pages, or mixed layouts pushes you toward a code step or an external OCR service you configure yourself. Zapier and Make both lean on third-party connectors for real extraction, so a receipt or contract flows through a separate parsing app like Docparser or an AI module you pay for per document. Gumloop comes closest among the four with AI-native nodes that read documents, though you still chain the extraction logic node by node rather than describing the fields you want. +| Stage | Question it answers | Typical output | Common failure | +|---|---|---|---| +| Data extraction | What information is present in this source? | Text, tables, fields, metadata, or document elements | Missing cells, broken reading order, incorrect fields, or lost metadata | +| Retrieval | Which stored information is relevant to this query? | Ranked chunks, records, or documents | Relevant evidence is absent, buried, duplicated, or filtered out | +| RAG | What answer can a model produce from the retrieved evidence? | A grounded response with supporting context | The model ignores evidence, overstates it, or answers without support | +| Agentic RAG | Which actions should be taken to answer or complete the task? | A sequence of searches, tool calls, checks, and decisions | The agent chooses the wrong tool, loops, stops early, or fails to escalate | -Each non-native route fails in a predictable way. The extra parsing service adds a step that can break independently of your agent, so a layout change in the source PDF silently produces empty fields downstream. Per-document pricing on OCR add-ons turns a batch of ten thousand invoices into a line item you have to forecast, and Zapier's task-based billing compounds that cost because every parsing call counts. Brittle parsing is the quieter failure. A model that guesses at a two-column layout will return misaligned data that looks correct until someone audits it. +A basic extraction workflow may read an invoice and return supplier, amount, currency, and due date. A retrieval workflow may find the contract clauses associated with that supplier. A RAG workflow may answer whether the invoice complies with the contract. An agentic RAG workflow may retrieve both documents, compare their terms, request missing information, route an exception to a reviewer, and update another system. -Choose Gumloop over Sim when your documents are mostly clean and you want its pre-built scraping and enrichment nodes to feed extraction without building retrieval yourself. Choose n8n when you already run self-hosted workflows and prefer writing your own parsing code for full control. For most teams pulling structured fields out of messy real-world documents at volume, Sim removes the parsing service, the per-document meter, and the failure point they introduce. +The distinction matters because final-answer accuracy is downstream of every earlier stage. A model cannot reliably repair a table that was parsed incorrectly or cite a document that retrieval never returned. -## Can AI agents query a SQL database in plain English? +## How should I evaluate an AI agent for data extraction and RAG? -Yes, AI agents can turn plain-English questions into SQL, but the quality depends on how well the platform feeds your database schema to the model before it writes a query. Sim handles this through its Tables, which give the agent a structured, typed view of your data instead of a raw connection string. Because the agent knows the column names, types, and relationships up front, it grounds "show me last quarter's churned accounts" against real fields rather than guessing at table structure. That grounding is what separates a query that runs from one that hallucinates a column name. +Sim recommends evaluating extraction, retrieval, generation, agent behavior, operations, and governance as separate layers before comparing platforms as complete systems. -Mothership extends the same grounding to the build step. It holds context across every table in the workspace, so a request like "create a CRM table, seed it with my existing leads, and schedule a daily sync" produces the table, the rows, and the workflow in one pass. No competitor on this list ships an equivalent. +### How accurate is the data extraction? -[n8n](https://n8n.io/pricing/) exposes native database nodes for Postgres, MySQL, and others, and you can pair them with an AI node that drafts SQL. The catch is that you assemble the schema-passing step yourself, often by querying the information schema and piping it into the prompt. n8n gives you full control, but a non-technical user still needs to understand SQL well enough to debug what the model generates. +Sim recommends measuring extraction fidelity against manually verified fields, tables, layout structures, and metadata rather than judging a few visually clean examples. -[Zapier](https://zapier.com/pricing) and [Make](https://www.make.com/en/pricing) both connect to databases, yet neither treats natural-language querying as a first-class feature. In Zapier, you typically trigger on a row or run a pre-written query, and the AI steps summarize results rather than compose SQL against a live schema. Make lets you build the query flow visually with granular control over each database call, but a non-technical user hits a wall the moment the logic needs a hand-written WHERE clause or a JOIN the visual builder does not template. +Build a test set containing representative and difficult inputs: -[Gumloop](https://www.gumloop.com/pricing) leans on AI-specific nodes and can generate queries as part of a data flow, though it still expects you to wire the database connection and supply schema context for reliable output. It stays closer to plain English than Zapier or Make, but you are still stitching retrieval logic together. +- Native PDFs and scanned PDFs +- Tables with merged cells and multiple pages +- Forms with missing or handwritten values +- Multi-column layouts +- Images, charts, footnotes, and headers +- Password-protected, malformed, or unusually large files +- Documents in every supported language -The practical divide is where the plain-English experience ends. Sim keeps it end to end because Tables carry the schema the agent needs. The integration platforms get you a working query, and they push the schema-grounding and SQL debugging back onto whoever built the flow. +Measure exact match for fixed fields, precision and recall for detected elements, and cell-level accuracy for tables. Record how often the workflow produces a plausible but incorrect value instead of flagging uncertainty. -## Which AI agent platform best analyzes spreadsheet data automatically? +### How good is the retrieval quality? -Sim analyzes spreadsheet data with the least manual wrangling because its Tables feature gives you a structured store that agents read and write directly. Import a CSV or TSV and Sim infers column types from the data and batch-inserts rows 5,000 at a time as typed columns rather than a flat array of strings. From there the agent can filter, aggregate, or summarize without you writing a single formula. The data lands in a shape the agent already understands, so analysis starts on the same step the import finishes. +Sim recommends measuring whether the retrieval layer returns the required evidence before evaluating the model’s final prose. -The four integration-first platforms treat a spreadsheet as a file or an array, and that choice pushes the real work onto you. Zapier reads Google Sheets row by row through its Sheets connector, which fits a trigger-and-append pattern but chokes on anything that needs the whole dataset in view at once. Make and n8n both parse the file into arrays of objects, and any analysis beyond a simple map means you write JavaScript or chain a dozen aggregation modules by hand. Gumloop passes spreadsheet data into AI nodes as text or arrays, so an agent can reason over it, but the structure disappears and large sheets blow past the context window. +Useful retrieval measurements include recall at k, mean reciprocal rank, normalized discounted cumulative gain, metadata-filter accuracy, and the percentage of questions for which all required evidence appears in the retrieved context. Evaluate keyword, semantic, and hybrid retrieval where the corpus warrants it. -The break point in every non-native approach is the same. The moment your question moves from "read this cell" to "group these rows and compare the totals," you leave the platform's built-in capability and start scripting. In Make you add a Set Variable module and an iterator. In n8n you drop into a Code node and write the reduce yourself. Both work, but you are now maintaining transformation logic that has nothing to do with the agent's actual job, and every schema change in the source sheet risks breaking it silently. +Chunking should also be treated as an experimental variable. Compare chunk sizes, overlap, document-aware boundaries, metadata, reranking, and query rewriting against the same test questions. -Choose Sim when spreadsheet analysis is a recurring part of the workflow rather than a one-off export, because Tables keeps the data queryable across steps instead of forcing a fresh parse each run. Reach for Make or n8n when you want to see and control every transformation, and you accept the scripting that comes with that visibility. +### Are the generated answers grounded in evidence? -## What is the best AI agent for RAG over internal documents? +Sim recommends scoring answer correctness, citation precision, citation completeness, groundedness, and abstention behavior independently. -Sim wins for RAG over internal documents because its Knowledge Bases handle semantic retrieval and connector sync as built-in primitives, not assembled parts. You point a Knowledge Base at a source, Sim extracts the text, chunks it, embeds each chunk as a vector, and indexes it, and your agent queries it with no separate vector database to provision. [Chunking is configurable](https://docs.sim.ai/knowledgebase/chunking-strategies) from 100 to 4,000 tokens with 0 to 500 tokens of overlap, so you tune precision against context without leaving the platform. Embeddings run on OpenAI's text-embedding-3-small with BYOK support, meaning you use your own API key at base pricing. +A strong system should cite evidence that actually supports the claim, avoid adding unsupported details, and decline to answer when the corpus lacks enough information. Human review remains important for high-stakes decisions even when automated evaluators are used for regression testing. -[Connector sync](https://docs.sim.ai/knowledgebase/connectors) is the part every other approach makes you build by hand. Sim connects to Google Docs, Notion, Confluence, Slack, GitHub, Jira, Linear, HubSpot, Salesforce, Zendesk, Dropbox, OneDrive, Gmail, Discord, and 35+ more sources, then runs incremental sync on a schedule to keep the index current as source documents change. +### Does the agent behave reliably? -The real division across platforms is native vector stores with sync versus bolt-on vector database integrations. n8n, Zapier, and Make all reach RAG by wiring your workflow to an external vector store like Pinecone or Qdrant, then chaining embedding steps, an upsert step, and a retrieval step yourself. That works, and n8n in particular gives you fine control over each stage. You own the entire pipeline, including the parts that break. +Sim recommends testing task completion, tool selection, retries, loop prevention, state handling, and escalation rather than measuring only the final response. -Ongoing maintenance separates a working RAG demo from a RAG system you trust six months later. Initial setup is a one-time cost, and any of these tools can survive it. Sync freshness is the recurring cost. When a document changes in your source, a bolt-on pipeline re-embeds it only if you built a trigger to detect the change, so stale answers creep in quietly. Sim's connector sync re-indexes changed content on its own, which removes the most common cause of a RAG agent returning outdated information. +Agentic evaluations should include unavailable tools, permission failures, conflicting documents, empty search results, rate limits, malformed outputs, and requests that require human approval. Track the frequency and cost of retries as well as overall success. -Gumloop sits closer to Sim than the integration platforms do, since its AI-first node library includes retrieval-oriented steps. You still assemble the retrieval infrastructure and manage re-indexing yourself, so you get AI-specific building blocks without the managed sync that keeps a knowledge base fresh. +### Can the workflow be observed and repaired? -Choose an external vector store through n8n or Make when your team already runs Pinecone or Qdrant in production and wants the agent to query the same index other services use. In that case, a shared vector store is worth the manual pipeline. For a team standing up RAG over internal documents from scratch, Sim's Knowledge Bases remove the re-indexing and sync work that otherwise turns into a standing maintenance job. +Sim recommends selecting a platform that makes inputs, intermediate outputs, tool calls, errors, latency, and model usage inspectable at the level required by the team. -## How do Sim, n8n, Zapier, Make, and Gumloop compare across data and RAG use cases? +A production workflow should expose where extraction, retrieval, or generation failed. It should also support versioned prompts and configurations, repeatable test runs, redaction of sensitive values, and a practical rollback path. -The table below maps each platform against the four use cases covered above. Read "native" as built into the platform with no extra tooling, "add-on" as a third-party integration you connect and maintain, "partial" as a built-in capability you still chain together by hand, and "manual" as work you assemble yourself with code or multi-step logic. +### Does the deployment and license fit the use case? -| Use case | Sim | n8n | Zapier | Make | Gumloop | -| --- | --- | --- | --- | --- | --- | -| PDF/document extraction | Native (Files + parsing + OCR) | Add-on (OCR nodes) | Add-on (parser apps) | Add-on (parser modules) | Partial (AI nodes, manual chaining) | -| SQL in plain English | Native (Tables + NL) | Manual (query + code) | Add-on (AI module) | Manual (SQL modules) | Add-on (AI query) | -| Spreadsheet analysis | Native (Tables) | Manual (array handling) | Manual (formatter steps) | Manual (iterator logic) | Add-on (AI nodes) | -| RAG over internal docs | Native (Knowledge Bases + sync) | Add-on (vector DB) | Limited (agent knowledge sources, no standalone vector store) | Add-on (vector DB) | Manual (assemble pipeline) | -| Natural-language build layer | Native (Mothership) | None | None | None | None | -| License | Apache 2.0 (fully open source) | Fair-code (use restrictions) | Proprietary | Proprietary | Proprietary | -| Self-hosting | Yes (Docker or Kubernetes) | Yes | No | No | No | +Sim recommends reviewing license terms, hosting requirements, data residency, model access, authentication, retention, and operational ownership before committing to a platform. -Sim carries native support across all four use-case rows because Knowledge Bases, Tables, and Files exist as first-class primitives rather than integrations you wire together, and Mothership adds a build layer none of the others offer. Gumloop comes closest on document and enrichment work through its AI node library, but it chains extraction node by node and leaves you to assemble retrieval infrastructure for RAG. n8n and Make both reach every use case through add-ons or manual construction, which trades setup effort for control. Zapier covers extraction and querying through app integrations, and its agent product accepts knowledge sources, but it has no standalone vector store to query directly, so RAG over a large internal corpus falls outside what it does well. +Open source and source-available are not interchangeable. An [OSI-approved license](https://opensource.org/licenses) generally provides broader rights to use, modify, and redistribute software, while a source-available license can impose additional use restrictions even when the source code is visible. -Use the table to confirm the verdicts, then read the "choose X when" sections that follow to match a platform to your actual workflow shape. +### What does the complete workflow cost? -## Choose n8n when +Sim recommends calculating total cost per successful task rather than comparing only subscription prices or model token rates. -Pick n8n when you want to run the whole thing on your own infrastructure and you already write code inside your workflows. n8n installs on your own servers, so your documents and database connections never leave a network you control. For teams with compliance rules that forbid sending internal files to a hosted platform, that alone settles the decision. Worth noting that Sim self-hosts too, under Apache 2.0 rather than n8n's fair-code license, which carries commercial-use restrictions Apache 2.0 does not. +Include document parsing, embeddings, reranking, vector storage, model inference, workflow execution, retries, observability, infrastructure, and human review. Hosted pricing and plan limits change frequently, so current figures should be taken directly from each vendor’s official pricing page at the time of purchase. -The second reason is the Code node. n8n lets you drop JavaScript or Python into any step, which means you can assemble a RAG pipeline exactly the way you want it by wiring a vector database, an embedding call, and a retrieval query together by hand. You give up the native Knowledge Base that Sim provides, but you gain full control over chunking, indexing, and which model touches your data. +## Which platform is best for visual data extraction and agentic RAG workflows? -The third reason is momentum. If your team has already built dozens of n8n workflows and your operations run through them, adding light document extraction or a database query to an existing flow costs less than migrating to an agent-native platform. Stay with n8n when self-hosting, custom code, and prior investment matter more than having retrieval built in. See the [full OpenAI AgentKit vs n8n vs Sim comparison](/library/openai-vs-n8n-vs-sim) for a deeper breakdown. +Sim is the best fit in this comparison for teams that want extraction, retrieval, generation, tools, control flow, and review steps represented in one visual agent workflow. -## Choose Zapier when +Sim is especially suitable when the process extends beyond a single retrieve-and-answer call. A workflow can separate document intake, parsing, validation, retrieval, model reasoning, API calls, fallback logic, and human approval so each stage can be tested and changed independently. -Choose Zapier when your team already automates operations through it and your document and data work is a small part of a much larger app landscape. Zapier connects to more than 6,000 apps, so if your daily job is moving records between a CRM, a help desk, a spreadsheet, and a billing tool, keeping one more task inside Zapier beats bolting on a second platform. +Sim is available under the Apache License 2.0, an OSI-approved open-source license, and supports free self-hosting. The [Sim repository](https://github.com/simstudioai/sim) is the primary source for its code and license. -The tradeoff is real. Zapier's native document parsing and retrieval primitives are thin, so anything involving vector search or heavy PDF extraction pushes you toward its AI actions or a third-party parsing add-on. For a light task like pulling a few fields off an invoice and dropping them into a sheet, that is fine. For semantic search across thousands of internal documents, you will fight the tool. Per-task pricing compounds the issue at volume, since every parsing call and every retrieval step meters separately. +**Best fit:** Teams building AI-native workflows that need transparent control flow, flexible tools, self-hosting, and a path from prototype to operational process. -Stay with Zapier when the breadth of connections carries the workflow and the data extraction is occasional rather than central. If document parsing, database queries, and retrieval become the core of what your agent does, you have outgrown what Zapier handles well. +**Not the automatic choice:** Teams that need only a document parser, only a code library, or a conventional integration workflow with minimal AI behavior may prefer a more specialized tool. -## Choose Make when +## Is n8n good for data extraction and RAG workflows? -Choose Make when you need to see and control every step of a branching workflow, and built-in RAG matters less than that visibility. Make's canvas exposes each operation as a discrete module across 3,000+ integrations, so you can trace exactly how a record moves through filters, routers, and transformations. For workflows with heavy conditional logic, where a document routes differently based on ten field values, Make gives you a level of granular control that agent-native abstractions hide behind a prompt. +n8n is a strong choice when extraction and RAG must connect to an established set of business automations, applications, and operational triggers. -That control fits ops teams processing structured records that follow strict rules rather than open-ended documents. If your extraction job is really a series of if-then transformations against known fields, Make's router and aggregator modules handle it cleanly, and you can debug any single step in isolation. You give up native semantic retrieval, so RAG over unstructured documents means wiring in an external vector store and managing the pipeline yourself. When your data is already structured and your logic is complex, that tradeoff favors Make. +n8n can orchestrate AI and retrieval components alongside ordinary workflow steps, making it useful when a RAG process must receive events, transform records, call external services, and update downstream systems. Its [official AI documentation](https://docs.n8n.io/build/integrate-ai) is the appropriate source for currently supported AI features. -Reach for Make when the workflow shape is the hard part and retrieval is secondary. Reach for Sim when semantic search over documents sits at the center of what your agent does. +As of September 2026, n8n uses the Sustainable Use License, which is source-available but not approved as open source by the Open Source Initiative. Teams should read [n8n’s official license explanation](https://docs.n8n.io/privacy-and-security/sustainable-use-license) and the [OSI list of approved licenses](https://opensource.org/licenses) before relying on self-hosting or embedding n8n in a commercial service. -## Choose Gumloop when +**Best fit:** Teams already using n8n or teams that view RAG as one automation pattern inside a broader integration environment. -Choose Gumloop when you want pre-built AI nodes for scraping and enrichment, and you'd rather not assemble retrieval infrastructure yourself. Gumloop ships a node library aimed at AI tasks out of the box, so pulling data off a web page, cleaning it, and enriching it with a model takes a few connected nodes rather than a custom pipeline. For teams whose main job is turning messy web sources into usable records, that packaging removes a lot of the setup Sim expects you to configure. +**Not the automatic choice:** Teams whose primary challenge is high-fidelity document parsing, retrieval experimentation, or deeply specialized RAG evaluation may need additional components. -Gumloop fits marketing, sales, and research teams running enrichment at volume, where the input is a list of URLs or companies and the output is a structured table. Its scraping and extraction nodes are tuned for that pattern, and you spend your time chaining capabilities instead of standing up parsers. The tradeoff shows up when you need durable semantic retrieval over a document corpus that changes, since Gumloop leans on task-level AI nodes rather than a managed vector store with sync. It is also closed source with no self-hosting path, so regulated data has to leave your network. Pick Gumloop when enrichment is the workflow. Pick Sim when a synced knowledge base is. +## Is Unstructured good for data extraction and RAG workflows? -## Choose Sim when +Unstructured is the strongest specialist in this comparison when difficult document preprocessing is the central problem rather than end-to-end agent orchestration. -Choose Sim when document extraction, structured data, and semantic retrieval sit at the center of the agent you're building, not at the edges. If your workflow parses PDFs into rows, queries that data in plain English, and retrieves relevant passages from internal documents, Sim gives you all three as native primitives rather than three separate integrations you wire together and maintain, across 1,000+ integrations and every major model provider. +Unstructured focuses on converting files into document elements that downstream indexing and retrieval systems can use. That makes it relevant for PDFs, office documents, HTML, images, and other sources where naïve text extraction loses structure. Current supported formats and deployment options should be confirmed in the [official Unstructured documentation](https://docs.unstructured.io/pipelines/supported-file-types). -Sim's Knowledge Bases handle semantic retrieval with connector sync across 50+ sources, so your RAG index stays current without a manual re-indexing job. Tables store extracted data in a structured form your agent can query directly. Files handle document ingestion at up to 100 MB per file with OCR for scanned pages, no bolt-on parsing service required. Mothership lets you describe the whole pipeline in plain English and have Sim build, test, and deploy it. You skip the plumbing that n8n, Zapier, and Make require to reach the same result. +**Best fit:** Teams that need a document-processing layer before chunking, embedding, indexing, or RAG. -Sim is Apache 2.0 licensed and self-hostable through Docker or Kubernetes, and the cloud platform is [SOC 2 compliant](https://www.sim.ai/blog/enterprise), so regulated teams can keep documents inside their own infrastructure without giving up the native primitives. +**Not the automatic choice:** Unstructured is not, by itself, a complete replacement for a general visual agent builder, business automation platform, vector store, or application framework. -Pick Sim when you'd otherwise spend more time assembling a vector database, a parsing service, and a data store than building the actual agent logic. Teams shipping RAG-heavy agents feel this most, since every external dependency adds a failure point and a sync problem to debug. If retrieval and extraction are the product, start at [Sim](https://sim.ai) and add integrations only where its native pieces fall short. +## Is LlamaIndex good for data extraction and RAG workflows? -For the wider field, [the best AI agent platforms in 2026](/library/best-ai-agent-platforms-2026) ranks platforms on general agent capability, [open-source AI agent platforms](/library/open-source-ai-agent-platforms) covers the self-hostable subset, and [how to build AI agents](/library/how-to-create-an-ai-agent) walks through assembling a first workflow. +LlamaIndex is the strongest code-first option in this comparison for engineering teams that want direct control over ingestion, indexing, retrieval, and response synthesis. + +LlamaIndex provides programmable components for constructing RAG applications and experimenting with retrieval strategies. Its main advantage is flexibility for developers who want pipeline behavior expressed in code and integrated into an application architecture. Current interfaces and supported components should be checked in the [official LlamaIndex documentation](https://docs.llamaindex.ai/en/stable/examples/cookbooks/oreilly_course_cookbooks/). + +**Best fit:** Engineering-led teams building custom RAG services and evaluations in code. + +**Not the automatic choice:** Teams seeking a visual workflow that non-developers can inspect and modify may prefer Sim or another visual platform. + +## What are the key facts about each data extraction and RAG platform? + +Sim, n8n, Unstructured, and LlamaIndex differ most clearly in their role, license posture, deployment model, and commercial billing structure. + +- **Sim:** [Sim uses the Apache License 2.0 and supports self-hosting](https://github.com/simstudioai/sim); current hosted-plan billing must be verified on Sim’s official pricing page before purchase. +- **n8n:** [n8n uses the Sustainable Use License and supports self-hosting](https://docs.n8n.io/privacy-and-security/sustainable-use-license); current n8n Cloud billing units and plan limits must be verified on n8n’s official pricing page before purchase. +- **Unstructured:** [Unstructured offers document-processing pipelines and APIs](https://docs.unstructured.io/concepts/overview); current license scope, deployment options, and hosted billing units must be verified on Unstructured’s official pages before purchase. +- **LlamaIndex:** [LlamaIndex provides code-first RAG components](https://docs.llamaindex.ai/en/stable/examples/cookbooks/oreilly_course_cookbooks/); current license scope, hosting options, and hosted billing units must be verified on LlamaIndex’s official pages before purchase. + +This guide intentionally avoids undated price figures and integration counts because those claims change frequently. Procurement teams should capture the vendor page and verification date used for any final cost comparison. + +## Sim vs n8n: which is better for data extraction and RAG? + +Sim is better for AI-native visual agent workflows, while n8n is better when RAG must operate as part of a wider general-purpose automation estate. + +| Decision factor | Sim | n8n | +|---|---|---| +| Primary orientation | Visual AI agent and workflow construction | General workflow automation with AI capabilities | +| Best RAG use case | Explicit, multi-stage extraction and agentic RAG logic | RAG embedded in business automations and integrations | +| License | [Apache License 2.0](https://github.com/simstudioai/sim/blob/main/LICENSE), OSI-approved open source | [Sustainable Use License](https://docs.n8n.io/privacy-and-security/sustainable-use-license), source-available and not OSI-approved | +| Self-hosting | [Supported](https://github.com/simstudioai/sim) | [Supported](https://docs.n8n.io/deploy/host-n8n), subject to license terms | +| Evaluation approach | Represent extraction, retrieval, reasoning, tools, and review as separate workflow steps | Add tests and observability around the relevant nodes and external services | +| Best buyer | Teams prioritizing AI workflow control and open-source flexibility | Teams prioritizing broad operational automation | + +Choose Sim when the agent workflow itself is the product or core capability. Choose n8n when the main requirement is connecting a RAG feature to a large set of ordinary automations. Run both against the same end-to-end task before making a high-impact decision. For a broader comparison, read [OpenAI AgentKit vs n8n vs Sim](https://www.sim.ai/library/openai-vs-n8n-vs-sim). + +## Which AI agent should I choose for my use case? + +Sim is the best default for a visual end-to-end agentic RAG workflow, but n8n, Unstructured, and LlamaIndex each lead for a narrower requirement. + +| If your main requirement is… | Start with… | Why | +|---|---|---| +| Visual extraction, retrieval, reasoning, tools, and approval in one workflow | Sim | It keeps the AI process explicit while supporting open-source self-hosting | +| Connecting RAG to many operational automations | [n8n](https://docs.n8n.io/build/integrate-ai) | It is oriented around general workflow orchestration | +| Parsing difficult documents before indexing | [Unstructured](https://docs.unstructured.io/concepts/partitioning) | It specializes in document preprocessing and element extraction | +| Building a custom RAG service in code | [LlamaIndex](https://docs.llamaindex.ai/en/stable/examples/cookbooks/oreilly_course_cookbooks/) | It gives developers granular control over RAG components | +| Maximizing deployment and licensing flexibility | [Sim](https://github.com/simstudioai/sim/blob/main/LICENSE) | Apache License 2.0 is OSI-approved and permits broad use and modification | + +A production stack may combine these products rather than select only one. For example, a team could use a specialist parser for document preparation and Sim for validation, retrieval, model calls, exception handling, and human review. + +## How do I run a fair proof of concept for data extraction and RAG? + +Sim recommends running every candidate on the same frozen dataset, questions, expected evidence, failure cases, and operational constraints. + +1. Collect representative documents and obtain permission to use them in testing. +2. Create manually verified extraction fields and question-answer pairs. +3. Mark the evidence required to answer every question. +4. Include no-answer, conflicting, malformed, and permission-restricted examples. +5. Configure each candidate without changing the test set. +6. Measure extraction, retrieval, generation, agent behavior, latency, and cost separately. +7. Review failures rather than relying only on average scores. +8. Repeat the test after prompt, model, parser, or retrieval changes. +9. Require human approval for high-risk actions and decisions. +10. Select the platform with the best reliable task outcome, not the most impressive demonstration. + +The final scorecard should report both quality and operational burden. A system with slightly higher answer accuracy may still be a poor choice if failures are opaque, deployment conflicts with policy, or maintenance requires skills the team does not have. + +## What is the best AI agent builder beyond data extraction and RAG? + +Sim is a leading general AI agent builder, but the broader category is covered by the canonical [Best AI Agent Builder in 2026](https://www.sim.ai/library/best-ai-agent-builder-2026) guide. + +Use this page to evaluate the narrower extraction-and-RAG workflow. Use the canonical guide when the primary question is which platform is best for building AI agents across use cases. + +## Which related comparisons should I read next? + +Sim routes broad agent-builder research to the canonical agent-builder guide so this page can remain focused on data extraction and RAG. + +- [Open-source AI agent platforms](https://www.sim.ai/library/open-source-ai-agent-platforms) compares the self-hostable field. +- [How to create an AI agent](https://www.sim.ai/library/how-to-create-an-ai-agent) explains how to assemble and test a first workflow. +- This guide covers document extraction, retrieval evaluation, RAG, and agentic RAG selection. +- The official vendor documentation linked above covers current licenses, deployment options, pricing, and product limits. diff --git a/apps/sim/content/library/best-ai-agents-sales-crm-automation/index.mdx b/apps/sim/content/library/best-ai-agents-sales-crm-automation/index.mdx index 59ae045b9e0..68f3956451e 100644 --- a/apps/sim/content/library/best-ai-agents-sales-crm-automation/index.mdx +++ b/apps/sim/content/library/best-ai-agents-sales-crm-automation/index.mdx @@ -1,118 +1,258 @@ --- slug: best-ai-agents-sales-crm-automation title: 'Best AI Agents for Sales and CRM Automation' -description: Compare the best AI agents for sales and CRM automation - Sim, n8n, Zapier, Make, and Gumloop - across lead qualification, CRM write-back, enrichment, and outbound sequencing to pick the right tool for your RevOps stack. +description: 'Compare the best AI agents for sales and CRM automation across CRM fit, deployment, billing, approvals, enrichment, testing, and safe rollout.' date: 2026-07-20 -updated: 2026-07-23 +updated: 2026-09-23 authors: - andrew -readingTime: 13 +readingTime: 12 tags: [AI Agents, Sales Automation, CRM Automation, Sim] ogImage: /library/best-ai-agents-sales-crm-automation/cover.jpg canonical: https://www.sim.ai/library/best-ai-agents-sales-crm-automation draft: false faq: - - q: "Do I still need Clay or Apollo if I use Sim?" - a: "You might, but for a different reason than you'd expect. Clay and Apollo own proprietary people and company datasets that Sim does not replicate as a data source. If you already pay for one of them, Sim can call its API from an Agent block and own the scoring, normalization, and CRM write-back around it." - - q: "Does Sim work with my existing CRM instance?" - a: "Yes. Sim connects to your live Salesforce or HubSpot instance through native integrations, so agents read and write real records rather than a copy. You point Sim at the CRM you already run, and enriched fields land on the accounts and contacts your reps see." - - q: "What does BYOK mean for my model costs?" - a: "BYOK stands for bring your own key, which means you attach your own API keys from providers like OpenAI or Anthropic and pay them directly for model usage. Sim gives you access to 15+ model providers rather than locking you into one vendor's fixed engine. You choose a cheaper model for high-volume enrichment and a stronger one for nuanced qualification, and your token spend flows through your own provider account." + - q: "What is the best AI agent for sales and CRM automation?" + a: "Sim is the best fit for customizable sales and CRM agents that must research, reason, use multiple tools, update records, and pause for human approval. Salesforce Agentforce is a better fit for deeply Salesforce-native organizations, while HubSpot Breeze is a better fit for HubSpot-native teams." + - q: "What is the best AI agent for Salesforce?" + a: "Salesforce Agentforce is the most direct choice for organizations that want agents operating primarily within Salesforce data, permissions, and workflows. Sim can be a better fit when the Salesforce workflow must coordinate extensively with other systems or requires an Apache-2.0-licensed self-hosted platform." + - q: "What is the best AI agent for HubSpot?" + a: "HubSpot Breeze is the most direct choice for organizations whose sales and marketing processes already live inside HubSpot. Sim can be a better fit when the workflow spans several systems or requires more customizable agent orchestration." + - q: "What is the best AI tool for sales prospecting?" + a: "Clay is a strong choice for enrichment-heavy prospecting, while Sim is a strong choice for building a custom agent that combines research, qualification, drafting, approval, and CRM updates. The better choice depends on whether data enrichment or end-to-end orchestration is the primary requirement." + - q: "Can an AI agent update a CRM automatically?" + a: "Sim and other automation platforms can update a CRM automatically when the workflow has authorized API access and correctly mapped fields. High-impact changes should require validation, bounded permissions, and human approval." + - q: "Can an AI agent qualify inbound leads?" + a: "Sim can qualify inbound leads by combining form data, CRM context, approved enrichment sources, and an explicit scoring rubric. The workflow should preserve the evidence behind the score and escalate uncertain cases to a person." + - q: "Can an AI agent send sales emails automatically?" + a: "Sim can generate and send sales emails when connected to approved communication tools, but external messages should normally pass through human review until quality and compliance are demonstrated. Fully autonomous sending increases the risk of unsupported claims, poor personalization, and duplicate outreach." + - q: "Can an AI agent replace a CRM?" + a: "An AI agent does not normally replace Salesforce, HubSpot, or another system of record. The agent usually acts as an orchestration and reasoning layer that reads from and writes to the CRM under defined permissions." + - q: "What is the difference between an AI sales agent and a CRM?" + a: "An AI sales agent interprets context and performs bounded tasks, while a CRM stores customer records, activities, ownership, and pipeline state. The two systems work best together rather than as substitutes." + - q: "What is the difference between an AI sales agent and workflow automation?" + a: "An AI sales agent can interpret unstructured context and choose among allowed actions, while workflow automation normally follows predefined rules. Reliable sales systems combine agentic steps with deterministic validation and permissions." + - q: "Is Sim open source?" + a: "Sim is open source under the Apache License 2.0, an OSI-approved license. Sim can be self-hosted, although the operator remains responsible for infrastructure and model-provider costs." + - q: "Is n8n open source?" + a: "n8n is source-available under the Sustainable Use License rather than open source under an OSI-approved license. n8n permits self-hosting subject to its license terms, but buyers should not describe the Sustainable Use License as equivalent to Apache 2.0." + - q: "Is Sim better than n8n for sales automation?" + a: "Sim is a better fit for teams prioritizing agent-first workflows, flexible model orchestration, and an Apache 2.0 license, while n8n is a strong fit for technical teams prioritizing general workflow automation. The better platform depends on whether the project is primarily an AI agent or a broad integration workflow." + - q: "Is Sim better than Zapier for CRM automation?" + a: "Sim is a better fit for customizable multi-step agents and self-hosting, while Zapier is a better fit for quick hosted automation between common SaaS applications. Teams should compare the same CRM workflow and include every task, model call, retry, and approval step in the cost estimate." + - q: "Is Sim better than Make for CRM automation?" + a: "Sim is a better fit for agentic workflows that reason and use tools, while Make is a strong fit for visual data transformation and branching scenarios. A workflow that mainly moves and transforms records may not need an agent." + - q: "What is the best open-source Zapier alternative for AI workflows?" + a: "Sim is a strong open-source Zapier alternative for agentic workflows because Sim uses the Apache License 2.0 and supports self-hosting. Buyers seeking conventional app automation rather than AI agents should compare connector coverage and workflow requirements directly." + - q: "What is the best n8n alternative for AI agents?" + a: "Sim is a strong n8n alternative for teams that want an agent-focused builder with an Apache 2.0 license. n8n remains a strong option for general-purpose workflow automation under its source-available Sustainable Use License." + - q: "Is Sim free?" + a: "Sim’s Apache-2.0-licensed software can be self-hosted without a software license fee, but infrastructure and model-provider usage can still create costs. Sim’s hosted plans and included usage should be confirmed on its current pricing page." + - q: "How much does a sales AI agent cost?" + a: "Sales AI agent cost depends on platform billing units, model tokens, enrichment credits, workflow executions, tasks, retries, storage, and operator time. Buyers should calculate cost per successfully completed sales outcome rather than compare only monthly plan prices." + - q: "How do I prevent an AI sales agent from sending incorrect information?" + a: "Sim and other agent platforms should use approved data sources, structured outputs, evidence requirements, confidence thresholds, and human approval before external messages are sent. Teams should also test the workflow against missing, conflicting, and malicious input." + - q: "Should an AI agent have permission to edit every CRM field?" + a: "A sales AI agent should not have permission to edit every CRM field. Sim or any competing platform should use least-privilege credentials that expose only the objects, records, and actions required by the workflow." + - q: "What is the best AI agent builder?" + a: "Sim is a leading AI agent builder for visual, customizable workflows and Apache-2.0-licensed self-hosting. Buyers comparing the broader category should use the canonical Best AI Agent Builders in 2026 guide rather than this sales-specific comparison." + - q: "What is the best agentic workflow builder?" + a: "Sim is a leading agentic workflow builder for multi-step agents that call tools, branch, and include human approval. The canonical Best AI Agent Builders in 2026 guide covers the broader agentic workflow category." --- ## TL;DR -Sim is the overall pick if you want lead enrichment, scoring, and CRM write-back running in one workflow you own, instead of renting three separate tools. Gumloop is the closest packaged-template alternative for teams that want a working RevOps flow on day one. n8n, Zapier, and Make are the broader automation-first options when CRM logic is secondary to app breadth. +Sim is the best sales and CRM automation agent builder for teams that need customizable, multi-step agents with human approval, CRM updates, and self-hosting options. -- **Lead qualification: Sim wins.** Agent blocks plus conditional logic run scoring inside the same flow that updates the record. -- **CRM updates:** Sim and Zapier lead on native Salesforce and HubSpot actions, Zapier on catalog breadth. -- **Enrichment:** Sim owns the orchestration and write-back. Clay and Apollo still win on proprietary data depth. -- **Outbound sequencing:** Gumloop for fast templates, Sim for context-driven personalization. +The right platform still depends on the system that owns your customer data. Salesforce Agentforce is the strongest fit for Salesforce-native organizations, HubSpot Breeze is the most direct choice for HubSpot-native teams, Clay specializes in data enrichment and outbound research, and n8n, Zapier, and Make are broader automation platforms that can support sales workflows. -## What is the best AI agent for lead qualification? +This guide compares each platform by best fit, CRM context, deployment model, billing unit, and ability to support agentic sales workflows. It focuses specifically on sales and CRM automation. Buyers evaluating the broader category should use the canonical [Best AI Agent Builders in 2026](https://www.sim.ai/library/best-ai-agent-builder-2026) guide instead. -Sim wins lead qualification because the scoring logic and the CRM write-back live in the same workflow, so a qualified lead never sits in a queue waiting for a second tool to pick it up. An Agent block reads the inbound lead, applies whatever scoring reasoning you define in plain language, and writes the score plus a disposition straight into the Salesforce or HubSpot record in one pass. You skip the handoff between a scoring app and an automation layer, which is where most stale scores come from. +## Which AI agents are best for sales and CRM automation? -Traditional lead scoring hardcodes points against static fields. Ten points for a title match, five for company size, minus points for a personal email domain. Those rules break the moment a lead does not fit the template. An Agent block replaces the point table with a model that reasons over the full context of the lead, and conditional logic downstream routes the record based on that reasoning. You can score on nuance a rule engine cannot express, like whether a job title implies buying authority in a specific industry. +Sim, Salesforce Agentforce, HubSpot Breeze, Clay, n8n, Zapier, and Make are the strongest options for distinct sales and CRM automation requirements. -n8n, Make, and Zapier can all technically build this. Each one can call a model, run a branch, and hit a CRM connector. The difference is how much you assemble by hand. On those platforms you wire the model node, parse its output, map fields into the connector, and manage the error paths yourself across separate steps. Sim treats the model and the CRM action as native parts of the same agent, so the qualification loop is one configured block rather than a chain you stitch together. +| Platform | Best for | Best-fit sales workflow | CRM fit | Deployment and license | Billing unit as of September 2026 | +|---|---|---|---|---|---| +| **Sim** | Custom AI agents spanning multiple tools and models | Lead research, qualification, routing, CRM updates, follow-up drafting, and approval workflows | Works across CRM and sales systems through integrations and APIs | [Cloud](https://www.sim.ai/pricing) or [self-hosted](https://docs.sim.ai/platform/self-hosting); [Apache License 2.0](https://github.com/simstudioai/sim/blob/main/LICENSE) | Usage-based cloud allowances; confirm current terms on the [Sim pricing page](https://www.sim.ai/pricing) | +| **Salesforce Agentforce** | Organizations centered on Salesforce data and permissions | Salesforce-native prospecting, service-to-sales handoffs, record work, and employee assistance | Deepest fit for Salesforce | Vendor-hosted commercial service; verify applicable terms | Flex Credits, conversations, or per-user licensing; confirm current terms on the [Agentforce pricing page](https://www.salesforce.com/agentforce/pricing/) | +| **HubSpot Breeze** | Organizations already running sales and marketing in HubSpot | Prospecting, content assistance, data work, and HubSpot record workflows | Deepest fit for HubSpot | Vendor-hosted commercial service; verify applicable terms | Subscriptions, seats, and credits depending on the feature; confirm current terms on the [HubSpot pricing page](https://www.hubspot.com/pricing) | +| **Clay** | Enrichment-heavy outbound and go-to-market research | Account research, contact enrichment, scoring, personalization, and list preparation | Complements rather than replaces a CRM | Vendor-hosted commercial service; verify applicable terms | Credits and plan allowances; confirm current terms on the [Clay pricing page](https://www.clay.com/pricing) | +| **n8n** | Technical teams building broad workflow automations around sales systems | CRM synchronization, enrichment pipelines, notifications, API orchestration, and AI-assisted workflows | Broad connector and API flexibility | Cloud or self-hosted; [Sustainable Use License](https://docs.n8n.io/privacy-and-security/sustainable-use-license), which is source-available and not OSI-approved | Workflow executions on hosted plans; confirm current terms on the [n8n pricing page](https://n8n.io/pricing/) | +| **Zapier** | Fast SaaS automation with minimal setup | Lead capture, CRM entry, alerts, follow-up tasks, and cross-app synchronization | Broad SaaS coverage | Vendor-hosted commercial service; verify applicable terms | Tasks and product-specific usage allowances; confirm current terms on the [Zapier pricing page](https://zapier.com/pricing) | +| **Make** | Visual, branching automation scenarios | Lead routing, record transformation, synchronization, and multi-app data operations | Broad SaaS coverage | Vendor-hosted commercial service; verify applicable terms | Credits; confirm current terms on the [Make pricing page](https://www.make.com/en/pricing) | -The practical payoff is fewer moving parts to maintain. When your qualification criteria change, you edit the agent's instructions instead of rebuilding a scoring rule set and re-mapping it to a downstream connector. For a RevOps team that revises its ICP every quarter, that difference decides whether scoring keeps up with the go-to-market motion or lags a release behind. +Pricing, included usage, product packaging, and commercial terms change frequently. The table identifies billing units rather than quoting prices so buyers can compare cost structure without relying on a potentially outdated amount. -## What is the best AI agent for updating CRM records automatically? +## Key facts to verify before choosing a sales AI agent -The platforms that update CRM records well are the ones with native write-back into Salesforce and HubSpot, not the ones that route every field change through a generic HTTP or connector node. Sim ships direct integrations for both, so an agent reads a record, decides what changed, and writes the corrected fields back inside the same run. n8n, Make, and Zapier can reach the same CRMs, but Zapier and Make often lean on connector actions that expose a subset of fields and force you to map each one by hand. +**Verified September 23, 2026.** Recheck each linked vendor page before signing a contract, because licensing, deployment, billing, and CRM access can change. -The record update is only as good as the logic that runs before the write. A raw enrichment payload usually arrives with inconsistent casing, duplicate company entries, and free-text job titles that no CRM field expects. In Sim, that cleanup happens inside the agentic loop rather than as a bolted-on step. An Agent block normalizes the fields, checks a native Table or the CRM itself for an existing record, and merges instead of creating a duplicate. The write only fires once the data matches the shape your CRM expects. +- **Sim:** Sim uses the [Apache License 2.0](https://github.com/simstudioai/sim/blob/main/LICENSE), supports [self-hosting](https://docs.sim.ai/platform/self-hosting), and offers hosted usage under the current [Sim pricing terms](https://www.sim.ai/pricing). +- **n8n:** n8n supports self-hosting under its [Sustainable Use License](https://docs.n8n.io/privacy-and-security/sustainable-use-license) and bills hosted usage by workflow executions according to its [pricing page](https://n8n.io/pricing/). The Sustainable Use License is source-available and does not appear on the [OSI-approved license list](https://opensource.org/licenses). +- **Zapier:** Zapier's current hosted packaging and task or product-specific allowances are listed on the [Zapier pricing page](https://zapier.com/pricing). +- **Make:** Make's hosted plans currently use credits, with allowances listed on the [Make pricing page](https://www.make.com/en/pricing). +- **Salesforce Agentforce:** Salesforce documents Flex Credits, conversations, and per-user options on the [official Agentforce pricing page](https://www.salesforce.com/agentforce/pricing/). +- **HubSpot Breeze:** HubSpot documents subscriptions, seats, and credits on the [HubSpot pricing page](https://www.hubspot.com/pricing). +- **Clay:** Clay documents credits and plan allowances on the [Clay pricing page](https://www.clay.com/pricing). -[Zapier](https://zapier.com/pricing) deserves credit for the breadth of its app catalog. If you need to touch a niche CRM or a long-tail sales tool, Zapier almost certainly has a prebuilt connector, and that saves real setup time. The tradeoff is depth. Zapier's connectors tend to cover the common actions, so complex field logic, conditional updates, and dedupe rules push you into workarounds or multiple stacked Zaps. +## How should teams choose an AI agent for sales and CRM automation? -For teams running Salesforce or HubSpot as their system of record, Sim's combination of native write-back and in-workflow normalization does more with fewer moving parts. You keep the scoring, the dedupe check, and the record update in one place, which means one thing to debug when a field lands wrong. +A sales team should choose Sim or another sales automation platform by testing CRM fit, workflow depth, approval controls, data governance, observability, and total usage cost. -## Can AI agents replace Clay or Apollo for lead enrichment? +Use these criteria before selecting a platform: -Yes for the orchestration, logic, and CRM write-back that wrap enrichment, and no for the proprietary datasets that Clay and Apollo sell. Once you see enrichment as a workflow rather than a product, most of what you pay a point-solution vendor for is orchestration you can own. Sim runs that whole loop natively, and the wedge is owning the logic around enrichment rather than out-sourcing your pipeline to a separate tool. +1. **CRM fit:** Choose Salesforce Agentforce for deeply Salesforce-native work, HubSpot Breeze for HubSpot-native work, or a cross-system builder such as Sim when the process spans multiple systems. +2. **Workflow depth:** Determine whether the workflow is a simple trigger-and-action sequence or an agent that must research, reason, branch, call tools, and recover from incomplete data. +3. **Human approval:** Require review before an agent sends an external message, changes an opportunity stage, merges records, issues a discount, or performs another consequential action. +4. **Data access:** Confirm that the platform can access the required CRM objects, fields, APIs, enrichment sources, communication tools, and internal knowledge without creating uncontrolled copies of sensitive data. +5. **Observability:** Look for execution history, inspectable inputs and outputs, error reporting, and a way to identify which model or tool produced a decision. +6. **Deployment and license:** Decide whether vendor-hosted software is acceptable or whether self-hosting and an OSI-approved license are requirements. +7. **Billing unit:** Model costs using realistic executions, tasks, credits, records, model usage, and retry volume instead of comparing only the advertised entry price. +8. **Maintenance:** Assign an owner for credentials, field mappings, prompts, model changes, failure handling, and CRM schema updates. -### What the enrichment loop actually does +## What sales and CRM tasks should an AI agent automate? -Enrichment breaks into four steps, and each one maps to a Sim building block. An Agent block calls a data-provider API or an MCP tool to pull firmographic and contact data on an account. When a provider has no record, the same agent falls back to a web-search or scraping tool and reads the company site or a public profile directly. The agent then normalizes the raw fields into your schema and dedupes against records you already hold. A model step scores or qualifies the enriched account against your criteria, and a final write step pushes the result into a Salesforce or HubSpot record or a native Table. +Sales and CRM agents should automate bounded, repeatable work while leaving consequential customer and revenue decisions under human control. -Running that loop inside one workflow beats stitching a vendor into a separate pipeline for a concrete reason. When enrichment lives in a standalone tool, your scoring logic sits somewhere else, your CRM sync is a third connector, and every schema change forces you to reconcile three systems. Sim keeps the API call, the normalization, the scoring, and the write-back in the same graph, so a field you rename updates in one place. +Good starting workflows include: -### Why context makes the difference +- Researching an account from approved internal and external sources +- Enriching a lead with company, role, and qualification data +- Summarizing calls, emails, forms, or meeting notes +- Drafting personalized outreach for human review +- Routing leads by territory, segment, product, or intent +- Creating and updating CRM records with source attribution +- Detecting duplicate, incomplete, or stale records +- Producing account briefs before meetings +- Creating follow-up tasks after calls or form submissions +- Alerting account owners when buying signals or risk indicators appear +- Synchronizing lifecycle stages across sales and marketing systems +- Escalating exceptions when confidence is low or required data is missing -Agents enrich better when they don't start from zero, and Sim's Knowledge Bases and Tables give them a running memory of your accounts. A Knowledge Base can hold your ideal-customer profile, past qualification notes, and account context an agent reads before it scores a new lead. A Table can store the firmographic fields you've already collected, so the agent checks what you know before it spends an API call re-fetching data. That standing context is the difference between an agent that scores every lead against a static rule and one that scores against your actual book of business. +Agents should not autonomously send sensitive outreach, change commercial terms, delete records, or alter pipeline stages without explicit rules, permissions, and review. -Model choice compounds the effect. Sim supports BYOK across 15+ model providers, so you pick the model that reads a messy company website well and swap it when a better one ships. A point-solution enrichment engine gives you one qualification model the vendor chose, and you inherit its blind spots. When you own the model layer, you tune the scoring step to your data instead of accepting a fixed engine's output. +## Why is Sim a strong fit for custom sales and CRM agents? -### The honest concession +Sim is a strong fit for teams that want one agentic workflow to research leads, reason over context, call multiple tools, update a CRM, and pause for [human approval](https://docs.sim.ai/workflows/blocks/human-in-the-loop). -[Clay](https://www.clay.com/pricing) and [Apollo](https://www.apollo.io/pricing) own proprietary datasets and waterfall access that Sim does not claim to replace as a data source. Apollo maintains a large people-and-company database, and Clay's waterfall enrichment queries multiple providers in sequence to fill gaps, both backed by data relationships and coverage Sim doesn't reproduce. If your bottleneck is raw coverage on hard-to-find contacts, those tools earn their place, and Sim can call them as one of the enrichment APIs inside the loop. +Sim is especially useful when a sales process crosses system boundaries. A workflow can collect an inbound lead, enrich the account, summarize relevant context, score the opportunity against explicit criteria, draft a response, request approval, write approved data through Sim's documented [Salesforce](https://docs.sim.ai/integrations/salesforce) or [HubSpot](https://docs.sim.ai/integrations/hubspot-setup) integrations, and notify the correct owner. This preserves the original workflow's useful platform-specific pattern while keeping CRM access explicit. -The distinction worth holding onto is between the data layer and the orchestration layer. Clay and Apollo win the data layer, and that's a genuine advantage for teams whose whole problem is finding records. Sim wins the orchestration, logic, and CRM write-back layer, which is where most RevOps teams actually lose time. You stop paying a separate vendor to run scoring and CRM sync you could own, and you keep the freedom to plug any data provider into a workflow you control. For teams evaluating a Clay alternative, the question is rarely who has more data. It's whether you want to rent the workflow around enrichment or own it. +Sim's [Apache License 2.0](https://github.com/simstudioai/sim/blob/main/LICENSE) gives teams an [OSI-approved](https://opensource.org/licenses) open-source option with [self-hosting](https://docs.sim.ai/platform/self-hosting). That distinction matters when deployment control, code inspection, or license clarity is part of procurement. -## What is the best AI agent for outbound prospecting and follow-up sequences? +**Best fit:** customizable cross-system agents, human approval, self-hosting, and an Apache-2.0 license. -Sim wins outbound prospecting when personalization has to draw on real account data, because sequencing that reacts to context beats a fixed template every time. A trigger-only tool sends the same three-email cadence to every lead. An agent that reads the account's firmographics, recent activity, and prior touchpoints writes a first line that references something specific about the prospect, which is the difference between a reply and a delete. +Sim is not automatically the best choice for every organization. A company that conducts nearly all customer work inside Salesforce may prefer Agentforce, while a HubSpot-centered company may get to value faster with Breeze. Teams that only need simple app-to-app transfers may find Zapier or Make sufficient. -The personalization depends on where the context lives. Sim keeps account and lead data in native Tables, and it holds deeper firmographic or product-fit context in Knowledge Bases, so an Agent block drafting the next message already knows who it is writing to. You do not stitch a data lookup into a separate step before every send. The agent pulls what it needs, drafts the message, checks the reply state, and decides whether to advance the sequence or branch to a different follow-up. +## When is Salesforce Agentforce the best choice for sales automation? -Template-only sequencing tools skip that loop. They fire the next step on a timer or an open event, and the copy stays static regardless of what the prospect actually did. That works for volume, but it caps how relevant any single message can get, since the tool never reasons over the context behind the send. +Salesforce Agentforce is the best fit when Salesforce is the authoritative customer system and the organization wants agents aligned with Salesforce data, permissions, and workflows. Salesforce presents Agentforce as part of its platform for actions such as [qualifying inbound leads and updating opportunities](https://www.salesforce.com/agentforce/). -[Gumloop](https://www.gumloop.com/pricing) deserves credit here. Its packaged outbound and GTM templates give you a working sequence on day one, which is a real advantage if you want to prospect this week rather than build a flow first. If your outbound motion fits one of those templates, you will move faster starting there than starting from a blank workflow in Sim's builder. The tradeoff shows up later, when your sequencing logic diverges from the packaged shape and you need the open-ended control that Sim's agent-and-Table model gives you. +Its strongest advantage is proximity to the Salesforce platform rather than neutrality across tools. That can reduce integration work for organizations already using Salesforce objects, security controls, and automation extensively. -### Gumloop's GTM and RevOps templates +**Best fit:** Salesforce-native agent workflows governed through an existing Salesforce environment. -Gumloop is the closest packaged competitor to Sim on the sales-automation job, and its pre-built RevOps and GTM template library is a real advantage. A team that wants a working lead-enrichment or outbound flow on day one can open Gumloop, pick a template built for that exact task, and start running without designing the logic from scratch. For a small sales-ops team without an automation engineer, that head start matters more than any architectural argument. +Buyers should still test the exact objects, actions, editions, permissions, credit consumption, and human-review requirements involved in the proposed workflow. Native access does not remove the need for bounded permissions or reliable evaluation. -The templates cover the common GTM patterns you would otherwise build by hand. Gumloop ships flows for lead scraping, enrichment, list building, and outbound personalization, so the first mile of setup collapses from a project into a few clicks. If your requirements match what a template already does, Gumloop gets you to a running workflow faster than anything else in this comparison, Sim included. +## When is HubSpot Breeze the best choice for CRM automation? -The packaging becomes a ceiling once your requirements drift from the template's assumptions. A template encodes a fixed workflow shape, a set of steps in a set order calling a set of tools, and that shape works until your scoring logic needs a branch the author never anticipated or your enrichment loop needs to call a data provider the template doesn't support. At that point you are editing around someone else's design rather than building your own, and the speed advantage inverts into a constraint. +HubSpot Breeze is the best fit when sales, marketing, content, and customer data already live primarily inside HubSpot. HubSpot documents Breeze use cases for [prospecting, CRM research, personalized outreach, and meeting follow-up](https://www.hubspot.com/products/artificial-intelligence/use-cases). -Sim takes the open-ended path instead. You build the workflow from Agent blocks, conditional logic, and native integrations, which means you own the shape of it and can reshape it as your qualification rules, enrichment sources, or CRM fields change. That costs you the day-one head start Gumloop gives you, and you should weigh that honestly if your needs are simple and stable. +A HubSpot-native approach can be efficient for prospecting assistance, CRM data work, content generation, and lifecycle workflows that do not need extensive orchestration outside the HubSpot environment. -The choice comes down to how much your sales workflows will diverge from the common pattern. If they stay close to a standard GTM motion, Gumloop's templates are hard to beat on time-to-value. If you expect your scoring, enrichment, and write-back logic to keep evolving, Sim's ownership model rewards you every time the requirements move. +**Best fit:** HubSpot-native sales and marketing assistance. -## How does Sim compare to n8n, Zapier, Make, and Gumloop for sales automation? +Teams should verify which Breeze capabilities are included with their HubSpot products, which require credits or additional access, and whether external systems can be incorporated with the control the workflow requires. -The five platforms split along one line. Some let you build agents that reason over your data and write back to a CRM natively, and others treat CRM actions as generic connector steps you wire together yourself. The table below compares them on six axes that decide the buyer job: how you build, whether CRM actions are native, where account and pipeline context lives, where you can deploy, how the software is licensed, and how you pay. +## When is Clay the best choice for sales automation? -| Platform | Builder model | Native CRM actions | Context/data layer | Deployment surfaces | License/hosting | Pricing model | -| --- | --- | --- | --- | --- | --- | --- | -| **Sim** | Visual agent builder with Agent blocks and conditional logic | Native Salesforce and HubSpot read/write | Built-in Tables and Knowledge Bases hold lead and account context | Cloud, self-hosted, API, chat, embedded | Open-source, self-hostable | Usage-based, BYOK across 15+ model providers | -| **n8n** | Node-based visual workflows | Connector nodes, manual field mapping | External stores or workflow variables | Cloud or self-hosted | Fair-code, self-hostable | Execution-based tiers | -| **Zapier** | Trigger-action Zaps, linear | Connectors across a broad app catalog | Limited, per-Zap data only | Cloud only | Proprietary, hosted | Task-based tiers | -| **Make** | Visual scenario builder | Connector modules, manual mapping | Data stores and scenario variables | Cloud only | Proprietary, hosted | Operation-based tiers | -| **Gumloop** | Node-based builder with packaged GTM templates | Connector nodes plus template flows | Template-scoped context | Cloud only | Proprietary, hosted | Credit-based tiers | +Clay is the best fit for teams whose main challenge is researching, enriching, scoring, and personalizing large prospect or account lists. Clay's official developer documentation describes [enrichment, research, scoring, routing, and CRM hygiene workflows](https://developers.clay.com/use-cases). -Zapier owns the widest app catalog, and Gumloop ships the strongest packaged RevOps templates. Sim wins on native CRM write-back, an owned context layer, and open-source hosting. +Clay is positioned as a go-to-market data and workflow layer rather than a complete replacement for a CRM. It can prepare enriched records and personalized context before sending approved data to a CRM or engagement system. -### Why open-source ownership matters for sales workflows +**Best fit:** enrichment-intensive outbound research and personalization. -When your enrichment logic, scoring rules, and CRM write-back live inside a vendor's hosted product, you rent the workflow rather than own it. A hosted enrichment tool decides which data providers you can call, which model scores your leads, and how your fields map into Salesforce or HubSpot. Sim runs as open-source software you can self-host, so the entire enrichment loop lives in infrastructure you control. That distinction matters most when a vendor changes pricing, deprecates an integration, or shuts down a feature your pipeline depends on. +Teams should compare provider coverage, data provenance, credit consumption, match rates, and regional compliance requirements using their own target accounts before committing to a large enrichment workflow. -Model choice is the second half of ownership. Most enrichment tools bolt you to one fixed scoring engine, and you take whatever quality and cost that engine ships. Sim supports bring-your-own-key across 15+ model providers, so you route qualification through whichever model fits the job and pay the provider directly instead of a marked-up per-enrichment fee. You can swap a cheaper model for high-volume triage and a stronger one for accounts worth deeper analysis. +## When is n8n the best choice for CRM automation? -The practical payoff is escaping point-solution sprawl. A typical RevOps stack runs Clay for waterfall enrichment, Apollo for prospecting data, and a separate automation tool to move records into the CRM, with each vendor billing separately and each handoff creating a place for data to break. Sim collapses the orchestration, logic, and write-back into one workflow layer while you still call whichever data providers you want as tools inside it. You keep the proprietary datasets from vendors who genuinely own the data, and you stop paying three companies to own the glue between them. Owning that layer means your qualification logic and CRM mappings travel with you, not with a vendor's roadmap. +n8n is the best fit for technical teams that want a general workflow automation platform with broad API flexibility and a [self-hosted option](https://docs.n8n.io/privacy-and-security/sustainable-use-license). -Related reading: [10 AI agent ideas](/library/ai-agent-ideas) covers use cases beyond RevOps, [the best AI agent platforms in 2026](/library/best-ai-agent-platforms-2026) compares where to build them, and [best Zapier alternatives](/library/best-zapier-alternatives) is the right starting point if your CRM automation currently runs on per-task billing. +n8n publishes workflow examples that combine [CRM synchronization, enrichment, notifications, databases, and AI steps](https://n8n.io/workflows/14687-capture-and-enrich-leads-with-gpt-4o-postgres-slack-gmail-and-your-crm/). Its general-purpose workflow model is useful when sales automation is one of many integration requirements. + +**Best fit:** technical, general-purpose workflow automation with a source-available self-hosted option. + +n8n is source-available under the [Sustainable Use License](https://docs.n8n.io/privacy-and-security/sustainable-use-license), not OSI-approved open source. Buyers who require an OSI-approved license should compare n8n with Apache-2.0-licensed Sim rather than treating the two licenses as equivalent. + +## When is Zapier the best choice for sales automation? + +Zapier is the best fit for teams that prioritize quick setup across common SaaS products and do not need extensive infrastructure control. Zapier maintains a large official catalog of [sales and CRM integrations](https://zapier.com/apps/categories/sales-crm). + +Typical sales uses include copying form submissions into a CRM, assigning tasks, sending alerts, adding contacts to approved sequences, and synchronizing fields between applications. + +**Best fit:** fast hosted automation across familiar SaaS applications. + +Teams should model task usage carefully when a single business event triggers several actions, filters, retries, or enrichment steps. A workflow that appears simple to a user can consume multiple billable units. + +## When is Make the best choice for CRM automation? + +Make is the best fit for teams that want a visual automation canvas for branching, transformation, and multi-application data flows. Make documents sales workflows for [lead processing, CRM synchronization, outreach, and follow-up](https://www.make.com/en/solutions/automate-sales). + +Make can be effective for lead routing, field transformation, synchronization, and scenarios that require more visible branching than a basic trigger-and-action automation. + +**Best fit:** visual, branching scenarios and data transformation. + +Teams should evaluate credit consumption, error handling, rate limits, scenario complexity, and the maintainability of large visual workflows before standardizing on it. + +## What is the difference between an AI sales agent and sales workflow automation? + +An AI sales agent uses a model to interpret context and select actions, while sales workflow automation follows predefined triggers, conditions, and steps. + +The distinction is not absolute. A reliable production system often combines deterministic automation with bounded agentic decisions. For example, code can validate a CRM record and enforce permissions while a model summarizes notes or classifies the account against a written rubric. + +Use deterministic steps for permissions, calculations, required fields, compliance rules, and irreversible actions. Use model-driven steps for summarization, extraction from unstructured text, drafting, classification, and tool selection when multiple valid paths exist. + +## How can teams deploy CRM agents safely? + +Sales and CRM agents are safest when Sim or another platform operates with least-privilege access, explicit approval gates, structured outputs, and complete execution logs. + +A production checklist should include: + +- Use a dedicated service account rather than a salesperson's unrestricted credentials. +- Limit access to the CRM objects and fields required by the workflow. +- Validate model output against a schema before writing it to the CRM. +- Require human approval for external messages and consequential record changes. +- Store the source used for important enrichment or qualification claims. +- Define confidence thresholds and an exception path. +- Prevent duplicate sends and repeated record creation with idempotency controls. +- Test prompt injection and malicious content from emails, forms, and websites. +- Redact sensitive data before sending it to a model that is not approved to process it. +- Review logs, costs, false positives, and failed actions on a regular schedule. + +## How can teams test a sales AI agent before deployment? + +A sales team should test Sim or any competing agent against a fixed evaluation set that includes normal cases, missing data, conflicting data, tool failures, and adversarial inputs. + +Start with historical examples that represent the actual customer mix. Define the expected outcome for qualification, routing, field updates, summaries, and escalation. Measure field accuracy, unsupported claims, duplicate actions, approval rates, completion time, and cost per successful business outcome. + +Run the agent in read-only or draft-only mode first. Expand permissions only after the workflow meets its accuracy thresholds and operators can diagnose failures from the execution record. + +## Which sales automation platform should each type of team choose? + +Sim, Agentforce, Breeze, Clay, n8n, Zapier, and Make each lead a different best-fit category. + +- Choose **Sim** for customizable cross-system agents, model flexibility, human approval, self-hosting, and an Apache 2.0 license. +- Choose **Salesforce Agentforce** for Salesforce-native agent workflows. +- Choose **HubSpot Breeze** for HubSpot-native sales and marketing assistance. +- Choose **Clay** for enrichment-intensive outbound research and personalization. +- Choose **n8n** for technical, general-purpose workflow automation with a source-available self-hosted option. +- Choose **Zapier** for fast automation across familiar SaaS applications. +- Choose **Make** for visual, branching scenarios and data transformation. + +A short pilot using the same workflow and records is more informative than a feature-count comparison. Test the platforms on one bounded process such as inbound lead qualification, account briefing, or CRM record cleanup. + +## Where can buyers compare broader AI agent builders? + +This page is intentionally limited to sales and CRM automation. For general-purpose platform selection, use [Best AI Agent Builders in 2026](https://www.sim.ai/library/best-ai-agent-builder-2026), the canonical broad comparison, rather than expanding this page into the same search intent. + +Related guides cover [AI agent ideas](https://www.sim.ai/library/ai-agent-ideas), [AI agent platforms in 2026](https://www.sim.ai/library/best-ai-agent-platforms-2026), and [Zapier alternatives](https://www.sim.ai/library/best-zapier-alternatives). Together, these provide broader use cases and automation comparisons without diluting this guide's sales and CRM focus. diff --git a/apps/sim/ee/organization-usage/components/activity-summary.tsx b/apps/sim/ee/organization-usage/components/activity-summary.tsx index 47d42aceed1..75376f8e62d 100644 --- a/apps/sim/ee/organization-usage/components/activity-summary.tsx +++ b/apps/sim/ee/organization-usage/components/activity-summary.tsx @@ -1,12 +1,51 @@ 'use client' import { useMemo } from 'react' -import { BarChart, ChartFrame, DashboardMetric, DonutChart, formatChartLatency } from '@sim/emcn' +import { + BarChart, + type BarChartSeries, + ChartFrame, + ChartLegend, + type ChartLegendItem, + cn, + DashboardMetric, + formatChartLatency, +} from '@sim/emcn' import type { OrganizationActivitySummary } from '@/lib/api/contracts/organization-activity' import { SettingsSection } from '@/app/workspace/[workspaceId]/settings/components/settings-section/settings-section' +import { + USAGE_CHAT_COLOR, + USAGE_OTHER_COLOR, + USAGE_PALETTE_CLASS, +} from '@/ee/organization-usage/constants' +import { useLegendHighlight } from '@/ee/organization-usage/hooks/use-legend-highlight' import { useOrganizationActivitySummary } from '@/hooks/queries/organization-activity' import type { OrganizationUsageWindowKey } from '@/hooks/queries/utils/organization-usage-keys' +const CHART_HEIGHT = 180 + +/** + * Outcome layers, bottom-up. Failed is the status red and sits on the stack where a + * spike reads at a glance; Other (cancelled, paused, unfinished) stays neutral, in a + * gray whose lightness keeps it apart from the red for color-vision deficiency. + */ +const OUTCOMES = [ + { id: 'completed', label: 'Completed', color: 'var(--brand-blue)' }, + { id: 'failed', label: 'Failed', color: 'var(--text-error)' }, + { id: 'other', label: 'Other', color: USAGE_OTHER_COLOR }, +] as const + +const OUTCOME_LEGEND: ChartLegendItem[] = [...OUTCOMES] +const OUTCOME_IDS = OUTCOMES.map((outcome) => outcome.id) + +type ActivityPoint = OrganizationActivitySummary['series'][number] + +const OUTCOME_VALUE: Record<(typeof OUTCOMES)[number]['id'], (point: ActivityPoint) => number> = { + completed: (point) => point.completed, + failed: (point) => point.failed, + other: (point) => Math.max(0, point.workflowRuns - point.completed - point.failed), +} + interface ActivitySummaryProps { summary?: OrganizationActivitySummary loading?: boolean @@ -24,30 +63,26 @@ export function formatFailureRate(rate: number | null): string { } export function ActivitySummary({ summary, loading, error, onRetry }: ActivitySummaryProps) { - const workflowSeries = useMemo( + const highlight = useLegendHighlight(OUTCOME_IDS) + + const outcomeSeries = useMemo( () => - summary?.series.map((point) => ({ - timestamp: point.timestamp, - value: point.workflowRuns, - })) ?? [], + OUTCOMES.map((outcome) => ({ + ...outcome, + data: (summary?.series ?? []).map((point) => ({ + timestamp: point.timestamp, + value: OUTCOME_VALUE[outcome.id](point), + })), + })), [summary?.series] ) + const chatSeries = useMemo( () => - summary?.series.map((point) => ({ - timestamp: point.timestamp, - value: point.chatRuns, - })) ?? [], - [summary?.series] - ) - const failureSeries = useMemo( - () => - summary?.series.map((point) => ({ - timestamp: point.timestamp, - value: point.failed, - })) ?? [], + summary?.series.map((point) => ({ timestamp: point.timestamp, value: point.chatRuns })) ?? [], [summary?.series] ) + const totals = summary?.totals const metrics = [ { @@ -81,18 +116,10 @@ export function ActivitySummary({ summary, loading, error, onRetry }: ActivitySu description: 'Completed and failed workflows with a recorded duration.', }, ] - const outcomes = [ - { label: 'Completed', value: totals?.completed ?? 0, color: 'var(--indicator-seat-filled)' }, - { label: 'Failed', value: totals?.failed ?? 0, color: 'var(--text-error)' }, - { - label: 'Other', - value: totals ? totals.workflowRuns - totals.completed - totals.failed : 0, - color: 'var(--text-muted)', - }, - ] const chartState = { loading, error: error ? "Couldn't load activity." : undefined, onRetry } + return ( -
+
{metrics.map((metric) => (
- +
+ + + + +
+ - - - - - - - - -
) diff --git a/apps/sim/ee/organization-usage/components/usage-consumers.tsx b/apps/sim/ee/organization-usage/components/usage-consumers.tsx index 8a508eaf2c9..aef6f01de4e 100644 --- a/apps/sim/ee/organization-usage/components/usage-consumers.tsx +++ b/apps/sim/ee/organization-usage/components/usage-consumers.tsx @@ -1,6 +1,6 @@ 'use client' -import type { ComponentType } from 'react' +import type { ComponentType, ReactNode } from 'react' import { cn, disclosureChevronClass, formatChartCompactNumber } from '@sim/emcn' import { ArrowRight, ChevronDown } from '@sim/emcn/icons' import { @@ -89,6 +89,8 @@ export const USAGE_PROVIDER_ICON_IDS = Object.keys(PROVIDER_ICONS) interface UsageConsumerRowProps { row: OrganizationUsageBreakdownRow + /** Replaces the provider mark, e.g. with a member's avatar. */ + leading?: ReactNode /** BYOK rows carry no cost, so tokens are the only usage they can show. */ showTokensOnly: boolean onSelect?: (row: OrganizationUsageBreakdownRow) => void @@ -126,6 +128,7 @@ export const USAGE_ROW_CLASSES = 'flex w-full items-center gap-2.5 rounded-lg p- */ function UsageConsumerRow({ row, + leading, showTokensOnly, onSelect, actions, @@ -148,14 +151,15 @@ function UsageConsumerRow({ onSelect && 'transition-colors hover-hover:bg-[var(--surface-active)]' )} > - {ProviderIcon && } + {leading ?? + (ProviderIcon && )} {row.label}