Workflow Node Catalog
Implementation Status
This catalog defines all node types available in the workflow builder canvas (UI palette). However, only the following node types are currently executable at runtime:
- Lens — fully implemented via the execution provider registry
- Lens Execute — alias for Lens node execution
All other node types (Logic, Data, AI Primitives, Storage/IO, Communication, Integrations, Battle/Arena, Media) are designed and documented but do not yet have runtime execution implementations. They appear in the canvas palette for workflow design and planning purposes.
The execution engine currently supports DAG validation (cycle detection, binding completeness) and single-provider-per-node execution via the provider registry (openai, anthropic, google, mistral, ollama, fal-ai, research, pdf-export). Multi-node orchestration with inter-node data flow is scheduled for implementation.
Status key: 🟢 Executable | 🟡 Schema & validation only | 🔴 Design-only (no runtime)
This is the human reference for the typed catalog in libs/infra/execution/src/lib/catalog/workflow-node-catalog.ts. Every row includes the requested structure: Node, Purpose, Inputs, Outputs, Required Configuration, Optional Configuration, Example Configuration, Common Valid Connections, Common Invalid Connections, and Execution Notes.
Lenses 🟢
| Node | Purpose | Inputs | Outputs | Required Configuration | Optional Configuration | Example Configuration | Common Valid Connections | Common Invalid Connections | Execution Notes |
|---|---|---|---|---|---|---|---|---|---|
| Lens | Execute a LenserFight lens prompt with model and funding controls. | text or json prompt context. | text result with model metadata. | model_id. | param_overrides, funding_source, key_ref_id, local_key_id, retry policy. | model_id: openai:gpt-4.1-mini, funding_source: platform_credit, param_overrides: { tone: "concise" }; input { text: "Summarize weekly battle results" }; output { text: "Weekly digest..." }; downstream Email Send.body = $.text. | Prompt Template -> Lens, RAG Retriever -> Lens, Lens -> Output Parser, Lens -> Email Send. | Embedding -> Lens without query text. | Existing workflows keep model_id and param_overrides; execution normalizes model_id to modelId. |
Triggers 🟡
| Node | Purpose | Inputs | Outputs | Required Configuration | Optional Configuration | Example Configuration | Common Valid Connections | Common Invalid Connections | Execution Notes |
|---|---|---|---|---|---|---|---|---|---|
| Manual Trigger | Start a workflow manually with typed root inputs. | none. | json rootInputs. | none. | inputSchema. | inputSchema: { query: { type: "string", required: true } }; input {}; output { rootInputs: { query: "Which strategy won?" } }; downstream RAG Retriever.query = $.rootInputs.query. | Manual Trigger -> RAG Retriever, Manual Trigger -> HTTP Request. | Manual Trigger -> Image Upscale without image input. | Browser and worker safe; production publishing can reuse the same input schema. |
| Schedule Trigger | Start a workflow on a cron schedule. | none. | json schedule metadata. | cron. | timezone, enabled. | cron: "0 8 * * MON", timezone: "Europe/Istanbul"; input {}; output { firedAt: "2026-05-18T08:00:00+03:00" }; downstream Supabase Query.params.since = $.firedAt. | Schedule Trigger -> Supabase Query, Schedule Trigger -> RSS Feed. | Schedule Trigger -> Output Parser without a text transform. | Scheduled execution environment; draft edits should not affect published schedule until publish. |
| Webhook Trigger | Start a workflow from an inbound HTTP request. | none. | json { body, headers, method }. | path. | method, secretRef, response mode. | path: "/hooks/github-pr-review", method: "POST", secretRef: "github-webhook-secret"; output { body: { action: "opened", pull_request: { number: 42 } } }; downstream GitHub PR Review.prNumber = $.body.pull_request.number. | Webhook Trigger -> Switch, Webhook Trigger -> GitHub PR Review. | Webhook Trigger -> Email Send without mapping subject/body. | Server or worker only; validate signature before execution. |
| Event Trigger | Start from a LenserFight domain event. | none. | json event envelope. | eventType. | workspaceId, filters. | eventType: "battle.completed", workspaceId: "{{workspace.id}}"; output { eventType: "battle.completed", payload: { winner: "sourced" } }; downstream Slack Notify.text = $.payload.winner. | Event Trigger -> Slack Notify, Event Trigger -> Leaderboard Update. | Event Trigger -> Speech To Text without audio. | Worker/server event bus; useful for production automation. |
| Form / Input Trigger | Start from a rendered user form. | none. | json submitted fields. | fields. | submitter policy, validation messages. | fields: [{ key: "prompt", type: "textarea", required: true }, { key: "contenders", type: "array", required: true }]; output { fields: { prompt: "Judge these answers", contenders: ["concise","sourced"] } }; downstream Battle Execute.prompt = $.fields.prompt. | Form Trigger -> Battle Execute, Form Trigger -> Prompt Template. | Form Trigger -> Vector Search without embedding. | Browser entry point; validation runs before DAG execution. |
Logic 🔴
| Node | Purpose | Inputs | Outputs | Required Configuration | Optional Configuration | Example Configuration | Common Valid Connections | Common Invalid Connections | Execution Notes |
|---|---|---|---|---|---|---|---|---|---|
| Code | Run sandboxed JavaScript/TypeScript against upstream data. | any. | json result. | code. | timeoutMs. | code: "return { digestText: input.summary, itemCount: input.items.length }", timeoutMs: 5000; output { result: { digestText: "3 battles changed rank", itemCount: 12 } }; downstream Email Send.body = $.result.digestText. | Any -> Code, Code -> Email Send, Code -> Data Mapper. | Code -> Speech To Text without audio output. | Browser/worker sandbox; keep deterministic and time-limited. |
| Switch | Route execution based on matching cases. | any. | text branch. | cases. | inputPath, defaultBranch. | inputPath: "$.status", cases: [{ label: "failed", operator: "equals", value: "failed" }], defaultBranch: "ok"; output { branch: "failed" }; downstream Slack Notify.text = $.branch. | Webhook Trigger -> Switch, Classifier -> Switch. | Switch -> Embedding without branch text mapping. | Edge labels can represent branch names in a later UI pass. |
| If / Condition | Continue when a boolean condition passes. | any. | boolean passed. | condition. | else branch label. | condition: "$.score >= 0.8"; output { passed: true }; downstream Leaderboard Update.approved = $.passed. | Judge / Eval -> If, If -> Slack Notify. | If -> Image Analyze without image. | Warning edges stay editor-only; execution uses branch evaluation. |
| Loop / Map | Iterate or map array items. | array or json. | array items. | none. | arrayPath, itemVariable, maxItems. | arrayPath: "$.documents", itemVariable: "document", maxItems: 25; output { items: [{ title: "Battle recap", score: 0.91 }] }; downstream Summarizer.documents = $.items. | RSS Feed -> Loop / Map, Loop / Map -> Summarizer. | Loop / Map -> Email Send without aggregation. | Use Split In Batches for rate-sensitive APIs. |
| Wait / Delay | Pause execution until a duration or timestamp. | any. | json resume metadata. | none. | delayMs, delayUntil. | delayMs: 300000; output { resumedAt: "2026-05-16T09:05:00Z" }; downstream GitHub Read.since = $.resumedAt. | Schedule Trigger -> Wait, Wait -> HTTP Request. | Wait -> Output Parser without text. | Worker/server execution should persist waiting state. |
| Error Catch | Convert upstream errors into a fallback path. | error. | json recovery payload. | none. | fallbackValue, continueOnError. | fallbackValue: { alert: "RSS summarization failed" }, continueOnError: true; output { recovery: { alert: "RSS summarization failed" } }; downstream Slack Notify.text = $.recovery.alert. | Any failing node -> Error Catch, Error Catch -> Slack Notify. | Error Catch -> Embedding without text/documents. | Does not hide errors unless explicitly configured. |
| Try / Catch | Protect a branch and expose result or error. | any. | json { ok, result, error }. | none. | catchBranch. | catchBranch: "notify_failure"; output { ok: false, error: { message: "Provider timeout" } }; downstream Slack Notify.text = $.error.message. | HTTP Request -> Try / Catch, Try / Catch -> Slack Notify. | Try / Catch -> Vector Search without embedding. | Useful for production workflows with fallback notifications. |
| Merge | Merge multiple upstream branches. | any. | json merged payload. | none. | strategy. | strategy: "json_object"; output { merged: { digest: "Ready", scores: [0.8, 0.9] } }; downstream Email Send.body = $.merged.digest. | Switch branches -> Merge, Merge -> Email Send. | Merge -> Speech To Text without audio. | Supports last_write_wins, concat, array, json_object. |
| Split In Batches | Process arrays in smaller batches. | array. | array batch. | none. | batchSize. | batchSize: 10; output { batch: [{ url: "https://example.com/feed/1" }] }; downstream RSS Feed.urls = $.batch. | Sheets Read -> Split In Batches, Split In Batches -> HTTP Request. | Split In Batches -> Email Send without summary. | Use with retry/rate limit for external APIs. |
| Sub-Workflow | Invoke another workflow with mapped inputs. | json. | workflow_result. | workflowId. | inputMapping, maxDepth. | workflowId: "wf_weekly_digest", inputMapping: { topic: "$.topic", window: "$.window" }, maxDepth: 2; output { workflowResult: { status: "completed", output: "Digest ready" } }; downstream Email Send.body = $.workflowResult.output. | Parent workflow -> Sub-Workflow, Sub-Workflow -> Stop / Return. | Sub-Workflow -> Image Upscale without image. | Guard recursion depth. |
| Stop / Return | End execution and return a final payload. | any. | workflow_result. | none. | returnPath, status. | returnPath: "$.answer", status: "completed"; output { workflowResult: { status: "completed", output: "Approved answer" } }; downstream Logger.message = $.workflowResult.output. | Any terminal branch -> Stop / Return. | Stop / Return -> required downstream production action. | Usually terminal; downstream Logger is only for debugging. |
Data 🔴
| Node | Purpose | Inputs | Outputs | Required Configuration | Optional Configuration | Example Configuration | Common Valid Connections | Common Invalid Connections | Execution Notes |
|---|---|---|---|---|---|---|---|---|---|
| JSON Transform | Transform JSON with an expression. | json. | json. | expression. | source path. | expression: "{ title: input.title, score: input.score }"; output { transformed: { title: "Lens A", score: 0.92 } }; downstream Data Mapper.input = $. | Webhook Trigger -> JSON Transform, JSON Transform -> Data Mapper. | JSON Transform -> Email Send without subject/body mapping. | Prefer structured mapping over string parsing. |
| Set Variables | Set workflow variables. | any. | json variables. | variables. | scope. | variables: { digestWindow: "7d", channel: "#arena-alerts" }; output { variables: { digestWindow: "7d" } }; downstream Prompt Template.variables = $.variables. | Manual Trigger -> Set Variables, Set Variables -> Prompt Template. | Set Variables -> Image Analyze without image. | Variable mutations are visible downstream. |
| Extract Field | Extract one field. | json. | text. | path. | default value. | path: "$.pull_request.body"; output { value: "This PR adds workflow runners." }; downstream Summarizer.content = $.value. | GitHub Read -> Extract Field, Extract Field -> Summarizer. | Extract Field -> Vector Search without embedding. | Use before Email Send subject/body mappings. |
| Rename Field | Rename payload fields. | json. | json. | renames. | preserve unknown fields. | renames: { body: "digestBody", title: "digestTitle" }; output { digestBody: "...", digestTitle: "Weekly digest" }; downstream Email Send.body = $.digestBody. | JSON Transform -> Rename Field, Rename Field -> Email Send. | Rename Field -> Speech To Text without audio. | Non-destructive by default. |
| Filter Items | Filter array items. | array. | array. | condition. | limit, empty behavior. | condition: "$.score >= 0.75"; output { items: [{ id: "doc_1", score: 0.91 }] }; downstream RAG Retriever.filters = $.items. | Sheets Read -> Filter Items, Filter Items -> Aggregate. | Filter Items -> Email Send without summarizer. | Keep filtered output shape stable. |
| Aggregate | Aggregate grouped values. | array. | json. | groupBy. | metrics. | groupBy: "$.contender", metrics: [{ field: "$.score", op: "avg", as: "averageScore" }]; output { groups: [{ contender: "A", averageScore: 0.84 }] }; downstream Score Aggregator.input = $.groups. | Vote Collector -> Aggregate, Aggregate -> Score Aggregator. | Aggregate -> Embedding without text/documents. | Useful before reports and leaderboards. |
| Sort | Sort items. | array. | array. | sortBy. | direction. | sortBy: "$.score", direction: "desc"; output { items: [{ id: "a", score: 0.98 }] }; downstream Leaderboard Update.scores = $.items. | Aggregate -> Sort, Sort -> Leaderboard Update. | Sort -> Output Parser without text. | Stable sort recommended for ties. |
| Deduplicate | Remove duplicate items. | array. | array. | keyPath. | keep strategy. | keyPath: "$.url"; output { items: [{ url: "https://example.com/post", title: "Arena news" }] }; downstream Summarizer.documents = $.items. | RSS Feed -> Deduplicate, Deduplicate -> Summarizer. | Deduplicate -> Text To Speech without text. | Use before expensive AI calls. |
| Text Splitter | Chunk long text/documents. | text. | document[]. | chunkSize. | chunkOverlap. | chunkSize: 1000, chunkOverlap: 120; output { documents: [{ pageContent: "Battle report chunk...", metadata: { chunk: 1 } }] }; downstream Embedding.documents = $.documents. | File Reader -> Text Splitter, Text Splitter -> Embedding. | Text Splitter -> Email Send without summarizer. | Required bridge for RAG indexing. |
| Data Mapper | Map fields into target schema. | any. | json. | mapping. | defaults. | mapping: { to: "$.owner.email", subject: "PR Review: {{title}}", body: "$.summary" }; output { to: "owner@example.com", subject: "PR Review: Add runners", body: "Review summary..." }; downstream Email Send = $. | Embedding -> Data Mapper, Data Mapper -> Email Send. | Data Mapper -> Image Upscale without image URL. | Primary bridge for incompatible edges. |
AI Primitives 🔴
| Node | Purpose | Inputs | Outputs | Required Configuration | Optional Configuration | Example Configuration | Common Valid Connections | Common Invalid Connections | Execution Notes |
|---|---|---|---|---|---|---|---|---|---|
| Prompt Template | Render a prompt. | any. | text prompt. | template. | variables. | template: "Summarize these results for {{audience}}: {{results}}", variables: { audience: "founders", results: "$.results" }; output { prompt: "Summarize..." }; downstream Lens Execute.prompt = $.prompt. | RAG Retriever -> Prompt Template, Prompt Template -> Lens Execute. | Prompt Template -> Image Upscale without image. | Safe browser render; no provider call. |
| Lens Execute | Execute a selected lens as a utility node. | text. | lens_result. | lensId, model_id. | param_overrides, funding fields. | lensId: "lens_weekly_digest", model_id: "openai:gpt-4.1-mini", param_overrides: { tone: "crisp" }; output { lensResult: { text: "Weekly digest..." } }; downstream Email Send.body = $.lensResult.text. | Prompt Template -> Lens Execute, Lens Execute -> Output Parser. | Embedding -> Lens Execute without text query. | Preserves legacy lens workflow compatibility. |
| Agent Execute | Delegate to a configured agent. | text. | agent_result. | agentId, task. | delegationPolicy. | agentId: "agent_pr_reviewer", task: "Review PR {{prNumber}} for security and tests", delegationPolicy: "approval_required"; output { agentResult: { status: "completed", summary: "Found 2 issues" } }; downstream GitHub PR Review.reviewBody = $.agentResult.summary. | GitHub Read -> Agent Execute, Agent Execute -> GitHub PR Review. | Agent Execute -> Image Analyze without image. | Approval policy can block delegation. |
| Output Parser | Parse model text into JSON. | text. | json. | none. | schema, strict. | schema: { score: "number", reasoning: "string", winner: "string" }, strict: true; output { score: 0.86, winner: "candidate_a" }; downstream Judge / Eval.candidates = $. | Lens -> Output Parser, Output Parser -> Email Send. | Embedding -> Output Parser. | Bridge from text to structured config. |
| Embedding | Convert text/documents into vectors. | text or document[]. | embedding vector with metadata. | provider, model. | inputPath, chunkSize, dimensions, metadataFields, retry. | provider: "openai", model: "text-embedding-3-small", inputPath: "$.documents[*].pageContent", chunkSize: 1000, dimensions: 1536, metadataFields: ["battleId","contenderId"]; output { embedding: { vector: [0.013,-0.024,0.071], dimensions: 1536 } }; downstream Vector Search.vector = $.embedding.vector. | Text Splitter -> Embedding, Embedding -> Vector Search. | Embedding -> Email Send. | Requires provider funding; not human-readable until mapped. |
| RAG Retriever | Retrieve scored documents. | text query or embedding. | document[]. | vectorStore, queryPath. | topK, similarityThreshold, filters, rerank. | vectorStore: "supabase:workflow_documents", queryPath: "$.rootInputs.query", topK: 6, similarityThreshold: 0.74, filters: { workspaceId: "{{workspace.id}}" }, rerank: true; output { documents: [{ pageContent: "Rubric...", score: 0.91 }] }; downstream Prompt Template.context = $.documents[*].pageContent. | Manual Trigger -> RAG Retriever, RAG Retriever -> Prompt Template. | RAG Retriever -> Email Send without summarizer/template. | Worker/provider funding; filters must match vector schema. |
| Vector Search | Search vector index directly. | embedding. | document[]. | vectorStore. | topK, filters. | vectorStore: "supabase:workflow_documents", topK: 5, filters: { battleId: "$.metadata.battleId" }; output { documents: [{ id: "doc_1", score: 0.88 }] }; downstream Summarizer.documents = $.documents. | Embedding -> Vector Search, Vector Search -> Summarizer. | Vector Search -> Email Send without text conversion. | Use RAG Retriever for query-text workflows. |
| Judge / Eval | Score candidates against a rubric. | json candidates plus rubric text. | json score, reasoning, winner, confidence. | rubric, scoringScale. | judgeModel, tieBreakRule, outputParser, confidenceThreshold. | rubric: "Score correctness, source coverage, actionability", scoringScale: "0-100", judgeModel: "anthropic:claude-3-7-sonnet", tieBreakRule: "prefer_higher_source_coverage", confidenceThreshold: 0.76; output { score: 91, winner: "answerA", reasoning: "More complete", confidence: 0.84 }; downstream Score Aggregator.score = $.score. | Output Parser -> Judge / Eval, Judge / Eval -> Score Aggregator. | Embedding -> Judge / Eval without candidate mapping. | Provider-funded; should use strict parser for production. |
| Memory Read | Retrieve workflow memory. | any query context. | document[]. | memoryKey. | limit, query. | memoryKey: "workspace:arena-digests", limit: 8, query: "$.query"; output { documents: [{ pageContent: "Last digest favored concise answers" }] }; downstream Prompt Template.memory = $.documents. | Manual Trigger -> Memory Read, Memory Read -> Prompt Template. | Memory Read -> Email Send without summary. | Server/worker memory store. |
| Memory Write | Persist workflow memory. | any. | json write status. | memoryKey, contentPath. | policy, metadata. | memoryKey: "workspace:arena-digests", contentPath: "$.summary", policy: "checkpoint", metadata: { source: "weekly_digest" }; output { written: true }; downstream Logger.message = $.memoryKey. | Summarizer -> Memory Write, Memory Write -> Logger. | Memory Write -> Embedding without content path. | on_success avoids storing failed-run state. |
| Chain | Run a configured AI chain. | any. | json. | steps. | chain timeout. | steps: [{ type: "prompt_template", template: "Summarize {{input}}" }, { type: "lens_execute", model_id: "openai:gpt-4.1-mini" }, { type: "output_parser", schema: { summary: "string" } }]; output { summary: "PR changes add catalog-backed nodes" }; downstream Slack Notify.text = $.summary. | RAG Retriever -> Chain, Chain -> Slack Notify. | Chain -> Speech To Text without audio. | Good for reusable AI pipelines; keep step count bounded. |
| Summarizer | Summarize text/documents. | text or document[]. | text. | model. | provider, inputPath, instructions. | provider: "openai", model: "gpt-4.1-mini", inputPath: "$.documents", instructions: "Create weekly digest email"; output { text: "Generated summary..." }; downstream Email Send.body = $.text. | RSS Feed -> Summarizer, Summarizer -> Email Send. | Summarizer -> Vector Search without embedding. | Provider-funded. |
| Classifier | Classify text into labels. | text. | json label/confidence. | model. | labels, threshold. | model: "gpt-4.1-mini", labels: ["security","tests","docs"], inputPath: "$.reviewBody"; output { label: "security", confidence: 0.87 }; downstream Switch.inputPath = $.label. | GitHub Read -> Classifier, Classifier -> Switch. | Classifier -> Image Upscale without image. | Use threshold before auto-actions. |
| Translator | Translate text. | text. | text. | model, target language. | source language, tone. | model: "gpt-4.1-mini", targetLanguage: "Turkish", inputPath: "$.founderNote"; output { text: "Yerellestirilmis not..." }; downstream Notion Write.Summary = $.text. | Lens -> Translator, Translator -> Notion Write. | Translator -> Embedding when untranslated documents are required. | Preserve locale metadata. |
| Image Analyze | Analyze an image. | image. | json observations. | model. | schema, moderation. | provider: "openai", model: "gpt-4.1-mini", imagePath: "$.image.url", schema: { safety: "string", caption: "string" }; output { caption: "Arena scorecard", safety: "ok" }; downstream Judge / Eval.candidates = $. | Object Storage Download -> Image Analyze, Image Analyze -> Judge / Eval. | Text Splitter -> Image Analyze. | Requires image-capable provider. |
| Audio Transcribe | Transcribe audio. | audio. | text. | model. | language, timestamps. | provider: "openai", model: "gpt-4o-transcribe", audioPath: "$.audio.url", timestamps: true; output { text: "The battle winner is..." }; downstream Summarizer.content = $.text. | File Reader -> Audio Transcribe, Audio Transcribe -> Summarizer. | Audio Transcribe -> Image Upscale. | Media artifact should be durable before worker execution. |
| Video Analyze | Analyze video. | video. | json notes. | model. | frame sampling, transcript. | provider: "openai", model: "gpt-4.1-mini", videoPath: "$.video.url", sampleEverySeconds: 5; output { scenes: ["scoreboard"], summary: "Battle replay..." }; downstream Summarizer.content = $.summary. | Object Storage Download -> Video Analyze, Video Analyze -> Summarizer. | Video Analyze -> Embedding without text extraction. | Long videos require worker/server execution. |
Battle / Arena 🔴
| Node | Purpose | Inputs | Outputs | Required Configuration | Optional Configuration | Example Configuration | Common Valid Connections | Common Invalid Connections | Execution Notes |
|---|---|---|---|---|---|---|---|---|---|
| Battle Create | Create a battle definition. | json. | json battle id/status. | battleId or title/contenders. | visibility. | title: "RAG answer showdown", visibility: "workspace"; output { battleId: "battle_123", status: "draft" }; downstream Battle Execute.battleId = $.battleId. | Form Trigger -> Battle Create, Battle Create -> Battle Execute. | Battle Create -> Email Send without result text. | Server-side write path. |
| Battle Execute | Run contenders and judge strategy. | text prompt plus array contenders. | battle_result. | contenders, judgeStrategy. | promptSource, fundingSource, resultVisibility, retry. | contenders: [{ id: "concise", lensId: "lens_concise_answer" }, { id: "sourced", lensId: "lens_sourced_answer" }], promptSource: "$.prompt", judgeStrategy: "panel", fundingSource: "platform_credit", resultVisibility: "workspace"; output { battleId: "battle_123", winner: "sourced", scores: { sourced: 94, concise: 78 } }; downstream Leaderboard Update.scores = $.scores. | Form Trigger -> Battle Execute, Battle Execute -> Leaderboard Update. | Embedding -> Battle Execute without prompt/contenders. | Provider-funded and should publish result visibility explicitly. |
| Contender Run | Run a single contender. | text prompt. | lens_result. | contenderId. | lensId, model_id. | contenderId: "sourced", lensId: "lens_sourced_answer", model_id: "openai:gpt-4.1-mini"; output { contenderId: "sourced", output: "Answer with citations..." }; downstream Judge Battle.candidates = $. | Prompt Template -> Contender Run, Contender Run -> Judge Battle. | Contender Run -> Vector Search without embedding. | Use inside battle orchestration. |
| Judge Battle | Judge contender outputs. | json. | battle_result. | rubric. | judgeModel, tie rules. | rubric: "Prefer correctness and source coverage", judgeModel: "anthropic:claude-3-7-sonnet"; output { winner: "sourced", scores: { sourced: 94, concise: 78 } }; downstream Score Aggregator.input = $. | Contender Run -> Judge Battle, Judge Battle -> Score Aggregator. | Judge Battle -> Image Analyze without image. | Similar to Judge / Eval but battle-specific. |
| Vote Collector | Collect votes. | json battle context. | json votes. | battleId. | closeAfterVotes, voters. | battleId: "$.battleId", closeAfterVotes: 7; output { votes: [{ voter: "u1", contenderId: "sourced" }] }; downstream Score Aggregator.votes = $.votes. | Battle Execute -> Vote Collector, Vote Collector -> Score Aggregator. | Vote Collector -> Embedding without text. | Human-in-the-loop friendly. |
| Score Aggregator | Aggregate scores/votes. | json. | battle_result. | aggregation. | weights. | aggregation: "weighted_average", weights: { judge: 0.8, vote: 0.2 }; output { winner: "sourced", finalScore: 91.6 }; downstream Leaderboard Update.score = $.finalScore. | Judge Battle -> Score Aggregator, Vote Collector -> Score Aggregator. | Score Aggregator -> Speech To Text without audio. | Keep scoring scale explicit. |
| Leaderboard Update | Update arena leaderboard. | battle_result. | json update status. | leaderboardId. | scorePath, visibility. | leaderboardId: "arena_weekly", scorePath: "$.finalScore", visibility: "workspace"; output { updated: true, rank: 1 }; downstream Slack Notify.text = $.rank. | Battle Execute -> Leaderboard Update, Leaderboard Update -> Slack Notify. | Leaderboard Update -> Output Parser without text. | Server-side mutation; require workspace permissions. |
Storage And I/O 🔴
| Node | Purpose | Inputs | Outputs | Required Configuration | Optional Configuration | Example Configuration | Common Valid Connections | Common Invalid Connections | Execution Notes |
|---|---|---|---|---|---|---|---|---|---|
| Supabase Query | Run allowlisted Supabase RPC/query. | json params. | json rows/data. | rpcName. | params, timeout, retry. | rpcName: "workflows.fn_recent_battle_results", params: { window: "7d" }; output { rows: [{ battleId: "battle_123", winner: "sourced" }] }; downstream Aggregate.items = $.rows. | Schedule Trigger -> Supabase Query, Supabase Query -> Aggregate. | Supabase Query -> Speech To Text without audio. | Only allowlisted RPCs; review RLS/grants before production. |
| SQL Query | Run parameterized SQL. | json params. | json rows. | query. | connection, timeout. | query: "select title,winner from arena.battles where created_at >= :since", params: { since: "$.firedAt" }; output { rows: [{ title: "RAG showdown", winner: "sourced" }] }; downstream Data Mapper.input = $.rows. | Schedule Trigger -> SQL Query, SQL Query -> Data Mapper. | SQL Query -> Image Analyze without image. | Server-only; avoid raw user SQL. |
| KV Read | Read workflow KV. | any. | json value/found. | key. | namespace. | key: "digest:lastSuccessfulRun"; output { value: "2026-05-09T08:00:00Z", found: true }; downstream Supabase Query.params.since = $.value. | Manual Trigger -> KV Read, KV Read -> Supabase Query. | KV Read -> Embedding without text/documents. | Good for checkpoints. |
| KV Write | Write workflow KV. | any. | json written. | key. | valuePath, ttlSeconds. | key: "digest:lastSuccessfulRun", valuePath: "$.completedAt", ttlSeconds: 604800; output { written: true }; downstream Logger.message = $.written. | Stop / Return -> KV Write, KV Write -> Logger. | KV Write -> Image Upscale without image. | Keep TTL explicit for cache-like values. |
| File Reader | Read a file reference. | any URL/object ref. | file. | none. | source, bucket, objectKey, allowedDomains. | source: "object-storage", bucket: "workflow-inputs", objectKey: "$.fileKey"; output { file: { url: "https://storage.example.com/report.pdf", mimeType: "application/pdf" } }; downstream Media Convert.file = $.file. | Object Storage Download -> File Reader, File Reader -> Text Splitter. | File Reader -> Email Send without attachment mapping/body. | Enforce domain allowlist and size limits. |
| File Writer | Write content to a file destination. | text/json/file. | file. | destination, objectKeyTemplate. | bucket, contentPath, mimeType. | destination: "object-storage", bucket: "workflow-outputs", objectKeyTemplate: "digests/{{runId}}.md", contentPath: "$.summary", mimeType: "text/markdown"; output { file: { url: "https://storage.example.com/digests/run_1.md", mimeType: "text/markdown" } }; downstream Email Send.attachments = $.file. | Summarizer -> File Writer, File Writer -> Email Send. | File Writer -> Speech To Text unless file is audio. | New runner emits a durable write request for worker/server fulfilment. |
| Object Storage Upload | Upload a file. | file. | file. | bucket. | objectKeyTemplate, metadata. | bucket: "workflow-artifacts", objectKeyTemplate: "media/{{runId}}/{{filename}}", filePath: "$.file.url"; output { file: { url: "https://storage.example.com/media/run_1/image.png" } }; downstream Slack Notify.text = $.file.url. | Text To Image -> Object Storage Upload, Upload -> Slack Notify. | Upload -> Output Parser without text. | Server/worker handles bytes. |
| Object Storage Download | Download a file. | json object key. | file. | bucket. | objectKey. | bucket: "workflow-artifacts", objectKey: "$.objectKey"; output { file: { url: "blob:downloaded", mimeType: "image/png" } }; downstream Image Analyze.image = $.file. | Manual Trigger -> Object Storage Download, Download -> Image Analyze. | Download -> Embedding unless text is extracted. | Validate bucket permissions. |
| Webhook Send | Send outbound webhook. | json. | json status/response. | url. | method, headers, body mapping, retry. | url: "https://hooks.example.com/lenserfight", method: "POST", bodyPath: "$"; output { status: 202, responseBody: { accepted: true } }; downstream Logger.message = $.status. | Data Mapper -> Webhook Send, Webhook Send -> Logger. | Webhook Send -> Image Upscale without image. | Prevent SSRF/private URLs. |
| HTTP Request | Call an HTTP endpoint. | any. | json response. | url. | method, headers, body, timeout. | url: "https://api.github.com/repos/org/repo/pulls/42", method: "GET", headers: { Authorization: "Bearer {{secrets.github}}" }; output { status: 200, body: { title: "Add catalog" } }; downstream GitHub PR Review.input = $.body. | Webhook Trigger -> HTTP Request, HTTP Request -> Data Mapper. | HTTP Request -> Speech To Text without audio. | Use Secret Resolver for credentials. |
| GraphQL Request | Call GraphQL endpoint. | json variables. | json data. | query. | endpoint, variables, headers. | endpoint: "https://api.github.com/graphql", query: "query($owner:String!){repository(owner:$owner,name:\"lenserfight-web\"){name}}", variables: { owner: "ofcskn" }; output { data: { repository: { name: "lenserfight-web" } } }; downstream Summarizer.content = $.data. | Manual Trigger -> GraphQL Request, GraphQL Request -> Summarizer. | GraphQL Request -> Image Analyze without image. | Same network safety rules as HTTP Request. |
Communication 🔴
| Node | Purpose | Inputs | Outputs | Required Configuration | Optional Configuration | Example Configuration | Common Valid Connections | Common Invalid Connections | Execution Notes |
|---|---|---|---|---|---|---|---|---|---|
| Email Send | Send an email with templates and attachments. | text/json/file. | json delivery status/message id. | to, subject, body. | fromProfile, attachments, provider, retry, onParentFailure. | fromProfile: "founder-updates", to: "{{workspace.owner.email}}", subject: "LenserFight weekly AI digest", body: "{{$.summary}}", attachments: [{ name: "leaderboard.csv", filePath: "$.leaderboardFile.url" }], provider: "resend", retry: { attempts: 3, backoffMs: 2000 }; output { status: "sent", messageId: "resend_abc123" }; downstream Logger.message = $.messageId. | Summarizer -> Email Send, File Writer -> Email Send. | Embedding -> Email Send. | Worker/server delivery provider; direct vector output is invalid. |
| Slack Notify | Send Slack message. | any mapped text/json. | json message status. | channel or webhookUrl, text. | provider profile, retry. | channel: "#arena-alerts", text: "Battle {{$.battleId}} winner: {{$.winner}}"; output { status: "sent", ts: "1715850000.000100" }; downstream Logger.message = $.ts. | Error Catch -> Slack Notify, Leaderboard Update -> Slack Notify. | Embedding -> Slack Notify without mapping. | Use templates for structured payloads. |
| Discord Notify | Send Discord webhook message. | any. | json delivery. | webhookUrl. | content, embeds. | webhookUrl: "{{secrets.discordArenaWebhook}}", content: "New battle result: {{$.winner}}"; output { status: "sent", messageId: "discord_123" }; downstream Logger.message = $.messageId. | Battle Execute -> Discord Notify. | Vector Search -> Discord Notify without summary. | Server/worker external call. |
| Telegram Notify | Send Telegram chat message. | any. | json delivery. | chatId. | parse mode, disable preview. | chatId: "{{secrets.telegramOpsChat}}", text: "Workflow failed: {{$.error.message}}"; output { status: "sent", messageId: "tg_123" }; downstream Logger.message = $.messageId. | Error Catch -> Telegram Notify. | File Reader -> Telegram Notify without text. | Keep secrets server-side. |
| Push Notification | Send in-app/device push. | any. | json queued notification. | audience. | title, body, deep link. | audience: "workspace_admins", title: "Digest ready", body: "{{$.summaryTitle}}"; output { status: "queued", notificationId: "push_123" }; downstream Logger.message = $.notificationId. | Schedule Trigger -> Push Notification. | Embedding -> Push Notification without mapper. | Rate limit by audience. |
| SMS Send | Send SMS alert. | any. | json delivery. | to, body. | provider, country rules. | to: "{{workspace.owner.phone}}", body: "Critical workflow failed: {{$.workflowName}}"; output { status: "sent", messageId: "sms_123" }; downstream Logger.message = $.messageId. | Error Catch -> SMS Send. | Document[] -> SMS Send without summary. | Server-only; use sparingly due cost/compliance. |
Integrations 🔴
| Node | Purpose | Inputs | Outputs | Required Configuration | Optional Configuration | Example Configuration | Common Valid Connections | Common Invalid Connections | Execution Notes |
|---|---|---|---|---|---|---|---|---|---|
| GitHub Read | Read repo/PR/issue/file data. | json request. | json GitHub data. | repository. | resource, number, credentials. | repository: "ofcskn/lenserfight-web", resource: "pull_request", prNumber: 42; output { pullRequest: { title: "Add workflow catalog", files: 12 } }; downstream GitHub PR Review.input = $.pullRequest. | Webhook Trigger -> GitHub Read, GitHub Read -> Agent Execute. | GitHub Read -> Image Upscale without image. | Use GitHub token secret. |
| GitHub PR Review | Draft or submit PR review. | json review payload. | json review status. | repository, prNumber, reviewBody. | event, comments, credentials. | repository: "ofcskn/lenserfight-web", prNumber: "$.pullRequest.number", reviewBody: "$.summary", event: "COMMENT"; output { status: "submitted", reviewId: "PRR_kwDO" }; downstream Slack Notify.text = $.status. | Agent Execute -> GitHub PR Review. | Embedding -> GitHub PR Review without review body. | Human approval recommended for requested-changes events. |
| GitHub Issue Create | Create GitHub issue. | json issue data. | json issue id/url. | repository, title, body. | labels, assignees. | repository: "ofcskn/lenserfight-web", title: "Workflow catalog validation failure", body: "$.reasoning", labels: ["workflow","automation"]; output { issueNumber: 128, url: "https://github.com/ofcskn/lenserfight-web/issues/128" }; downstream Slack Notify.text = $.url. | Judge / Eval -> GitHub Issue Create. | Audio -> GitHub Issue Create without transcription. | Server-side credentials. |
| RSS Feed | Fetch RSS items. | any. | json items. | feedUrl. | limit, since. | feedUrl: "https://github.blog/feed/", limit: 10, since: "$.lastSuccessfulRun"; output { items: [{ title: "Actions update", link: "https://github.blog/..." }] }; downstream Summarizer.documents = $.items. | Schedule Trigger -> RSS Feed, RSS Feed -> Deduplicate. | RSS Feed -> Email Send without summary. | Cap item count before AI. |
| Notion Read | Read Notion pages/database rows. | json. | json results. | databaseId or pageId. | filter, credentials. | databaseId: "{{secrets.notionDigestDb}}", filter: { property: "Status", equals: "Ready" }; output { results: [{ title: "Arena notes" }] }; downstream Summarizer.content = $.results. | Schedule Trigger -> Notion Read. | Notion Read -> Image Analyze without image URL. | Credentials server-side. |
| Notion Write | Write page/database row. | json. | json page id/url. | databaseId, properties. | parent page, icon. | databaseId: "{{secrets.notionDigestDb}}", properties: { Name: "$.title", Summary: "$.summary" }; output { pageId: "notion_page_123", url: "https://notion.so/..." }; downstream Slack Notify.text = $.url. | Summarizer -> Notion Write. | Embedding -> Notion Write without mapped properties. | Preserve Notion property types. |
| Sheets Read | Read Google Sheet rows. | json. | array rows. | spreadsheetId, sheetName or range. | value render option. | spreadsheetId: "{{secrets.weeklyMetricsSheet}}", sheetName: "Battle Metrics", range: "A2:G200", valueRenderOption: "FORMATTED_VALUE"; output { rows: [{ battleId: "battle_123", score: 91 }] }; downstream Aggregate.items = $.rows. | Schedule Trigger -> Sheets Read, Sheets Read -> Aggregate. | Sheets Read -> Speech To Text. | Google credentials required. |
| Sheets Write | Append/update Google Sheet rows. | json rows. | json update status. | spreadsheetId, sheetName, operation. | rowsPath, range. | spreadsheetId: "{{secrets.weeklyMetricsSheet}}", sheetName: "Digest Log", operation: "append", rowsPath: "$.rows"; output { updatedRows: 3, spreadsheetId: "sheet_123" }; downstream Logger.message = $.updatedRows. | Data Mapper -> Sheets Write. | Embedding -> Sheets Write without row mapping. | Normalize rows before write. |
| Calendar Create | Create calendar event. | json event. | json event id/link. | calendarId, title, start, end. | attendees, description. | calendarId: "primary", title: "Arena digest review", start: "2026-05-18T10:00:00+03:00", end: "2026-05-18T10:30:00+03:00", attendees: ["founder@example.com"]; output { eventId: "cal_123", htmlLink: "https://calendar.google.com/..." }; downstream Slack Notify.text = $.htmlLink. | If -> Calendar Create. | Calendar Create -> Embedding without text. | Timezone must be explicit. |
| Linear Issue Create | Create Linear issue. | json. | json issue id/url. | teamId, title. | priority, labels. | teamId: "{{secrets.linearTeamId}}", title: "Investigate low-confidence judge result", description: "$.reasoning", priority: 2; output { issueId: "LIN-321", url: "https://linear.app/..." }; downstream Slack Notify.text = $.url. | Judge / Eval -> Linear Issue Create. | Image -> Linear Issue Create without analysis text. | Credentials server-side. |
| Jira Issue Create | Create Jira issue. | json. | json issue key/url. | projectKey, issueType, summary. | labels, assignee. | projectKey: "LF", issueType: "Task", summary: "Workflow validation warning", description: "$.warning"; output { issueKey: "LF-42", url: "https://jira.example.com/browse/LF-42" }; downstream Slack Notify.text = $.url. | Error Catch -> Jira Issue Create. | Embedding -> Jira Issue Create without mapper. | Server-side credentials and project permissions. |
Media Generation 🔴
| Node | Purpose | Inputs | Outputs | Required Configuration | Optional Configuration | Example Configuration | Common Valid Connections | Common Invalid Connections | Execution Notes |
|---|---|---|---|---|---|---|---|---|---|
| Text to Image | Generate image from prompt. | text. | image. | model. | provider, size, funding. | provider: "fal-ai", model: "fal-ai/flux/dev", promptPath: "$.prompt", size: "1024x1024"; output { image: { url: "blob:image", mimeType: "image/png" } }; downstream Object Storage Upload.file = $.image. | Prompt Template -> Text to Image. | Embedding -> Text to Image without prompt. | Provider-funded media execution. |
| Image to Image | Transform an image. | image plus text prompt. | image. | model. | provider, strength. | provider: "fal-ai", model: "fal-ai/flux-pro/kontext", imagePath: "$.image.url", prompt: "Create a polished arena card"; output { image: { url: "blob:edited", mimeType: "image/png" } }; downstream Object Storage Upload.file = $.image. | File Reader -> Image to Image. | Text Splitter -> Image to Image without image. | Needs durable source image. |
| Image to Audio | Generate audio from image/prompt context. | image. | audio. | model. | prompt, duration. | provider: "fal-ai", model: "fal-ai/stable-audio", imagePath: "$.image.url", prompt: "Ambient intro for the battle replay"; output { audio: { url: "blob:audio", mimeType: "audio/mpeg" } }; downstream Object Storage Upload.file = $.audio. | Image Analyze -> Image to Audio with image passthrough. | RAG Retriever -> Image to Audio without image. | Provider-funded. |
| Text to Speech | Generate speech audio. | text. | audio. | voice. | provider, model, format. | provider: "openai", model: "gpt-4o-mini-tts", voice: "alloy", textPath: "$.summary"; output { audio: { url: "blob:tts", mimeType: "audio/mpeg" } }; downstream Object Storage Upload.file = $.audio. | Summarizer -> Text to Speech. | Embedding -> Text to Speech without text. | Media artifact persisted after execution. |
| Speech to Text | Transcribe speech. | audio. | text. | model. | language, timestamps. | provider: "openai", model: "gpt-4o-transcribe", audioPath: "$.audio.url"; output { text: "The battle winner is..." }; downstream Summarizer.content = $.text. | File Reader -> Speech to Text. | Text to Image -> Speech to Text unless output audio exists. | Use Audio Transcribe AI primitive for richer analysis. |
| Text to Video | Generate video from prompt. | text. | video. | model. | duration, aspect ratio. | provider: "fal-ai", model: "fal-ai/veo3", promptPath: "$.prompt", durationSeconds: 8, aspectRatio: "16:9"; output { video: { url: "blob:video", mimeType: "video/mp4" } }; downstream Object Storage Upload.file = $.video. | Prompt Template -> Text to Video. | Embedding -> Text to Video without prompt. | Worker/server for long jobs. |
| Image Upscale | Upscale an image. | image. | image. | scale. | provider, model. | provider: "fal-ai", model: "fal-ai/esrgan", imagePath: "$.image.url", scale: 2; output { image: { url: "blob:upscaled", mimeType: "image/png" } }; downstream Object Storage Upload.file = $.image. | Text to Image -> Image Upscale. | Email Send -> Image Upscale. | Requires image input. |
| Media Convert | Convert media/file format. | file. | file. | targetFormat. | bitrate, codec. | targetFormat: "mp3", inputPath: "$.file.url", audioBitrate: "128k"; output { file: { url: "blob:converted", mimeType: "audio/mpeg" } }; downstream Object Storage Upload.file = $.file. | File Reader -> Media Convert. | RAG Retriever -> Media Convert without file. | Worker/server byte processing. |
Utility 🔴
| Node | Purpose | Inputs | Outputs | Required Configuration | Optional Configuration | Example Configuration | Common Valid Connections | Common Invalid Connections | Execution Notes |
|---|---|---|---|---|---|---|---|---|---|
| Logger | Write structured logs. | any. | json logged status. | message. | level, includeInput. | level: "info", message: "Digest delivered: {{$.messageId}}", includeInput: true; output { logged: true, level: "info" }; downstream No-Op.input = $. | Any -> Logger. | Logger -> Image Upscale without image. | Safe terminal debug node. |
| Debug Inspector | Inspect payload shape. | any. | json inspection. | capturePaths. | redactSecrets. | capturePaths: ["$", "$.documents[0].metadata"], redactSecrets: true; output { inspection: { keys: ["documents","summary"] } }; downstream No-Op.input = $. | Any -> Debug Inspector. | Debug Inspector -> Email Send in production without mapper. | Manual/debug surface; redact secrets. |
| Secret Resolver | Resolve a secret reference. | any. | json secret alias. | secretRef. | exposeAs. | secretRef: "github-api-token", exposeAs: "githubToken"; output { githubToken: "{{resolved-secret-ref}}" }; downstream HTTP Request.headers.Authorization = $.githubToken. | Manual Trigger -> Secret Resolver, Secret Resolver -> HTTP Request. | Secret Resolver -> Logger with raw secret. | Server-only; never expose raw values to client logs. |
| Rate Limit | Throttle by key/window. | any. | json allowed/remaining. | limit. | key, windowSeconds. | key: "github-api", limit: 30, windowSeconds: 60; output { allowed: true, remaining: 29 }; downstream HTTP Request.input = $. | Split In Batches -> Rate Limit, Rate Limit -> HTTP Request. | Rate Limit -> Speech To Text without audio. | Use before paid/external APIs. |
| Cache Read | Read cached data. | any. | json hit/value. | key. | namespace. | key: "rss:github-blog:last-summary"; output { hit: true, value: { summary: "..." } }; downstream If / Condition.condition = $.hit. | Schedule Trigger -> Cache Read, Cache Read -> If. | Cache Read -> Image Analyze without image. | Use with Cache Write for RSS digests. |
| Cache Write | Write cached data. | any. | json written. | key. | valuePath, ttlSeconds. | key: "rss:github-blog:last-summary", valuePath: "$.summary", ttlSeconds: 3600; output { written: true }; downstream Logger.message = $.written. | Summarizer -> Cache Write. | Cache Write -> Embedding without text. | Keep TTL intentional. |
| Retry | Retry a branch. | any. | json attempts/succeeded. | attempts. | backoffMs, retryOn. | attempts: 3, backoffMs: 2000, retryOn: ["timeout","rate_limit"]; output { attempts: 2, succeeded: true }; downstream Logger.message = $.succeeded. | HTTP Request -> Retry, Retry -> Logger. | Retry -> Image Upscale without image. | Prefer node-level retry when possible. |
| No-Op | Pass input through unchanged. | any. | any. | label. | enabled. | label: "placeholder while designing PR review workflow"; output { output: "unchanged" }; downstream Logger.message = $.output. | Any -> No-Op. | No-Op as production terminal for required side effects. | Useful while designing templates. |