Knowledge Search
Find documents in a knowledge base by metadata, name or a query, and attach them.
Add a Knowledge Search node (knowledge_search) to find documents in one of your knowledge bases as an ordinary workflow step — no AI Agent in between. Each document comes back with its source, its metadata and, if you ask for it, a copy of the original file that Send Email or AI Prompt can attach.
This is the node for "a customer asked for a copy of invoice INV-2026-0142": look the invoice up by its number, attach it to the reply.
Configuration
| Field | Type | Default | Description |
|---|---|---|---|
knowledgeBaseId | string | '' | The knowledge base to search, from this workflow's project. |
knowledgeBaseName | string | '' | Its name, kept for display on the node card. |
query | string | '' | Optional. Text or an expression. Empty means an exact lookup. |
metadataFilters | list of { field, value, valueMode } | [] | Each row compares one of the knowledge base's declared metadata fields with a value, Fixed or an expression. All rows must match (AND). |
documentName | { operator: equals | contains, value } | none | Optional. Matches the document's filename, case-insensitive. |
maxResults | number | 10 | How many documents to return, 1–50. |
includeFiles | boolean | false | Adds each document's original as a file you can attach. See Files. |
At least one of query, a metadata filter or a document name must be set; a node with none of them refuses to run and says so. The same goes for a run in which every one of them resolved to empty text — searching by nothing would list the whole knowledge base, which is never what was meant.
The field dropdown lists the fields the knowledge base declares (under Knowledge → Advanced → Metadata fields), so you never type a field name. A value is compared as the field's own type, the same way it was stored when the document was uploaded: 42 and "42" both find a document whose int field is 42.
Exact lookup or search
Without a query, the node is an exact database lookup. It returns the documents whose metadata equals every filter and whose name matches, ordered by filename. There is no embedding call and no score, so the same input always returns the same documents in the same order — the right tool for "find INV-2026-0142".
With a query, the knowledge base's own search runs (vector, keyword or hybrid, whatever the knowledge base is set to), with the metadata filters applied. The best-matching passages are grouped by document, so maxResults counts documents rather than passages, and each document keeps its best score and the passage that earned it.
Only documents that have finished indexing are returned. A query search can also return web pages the knowledge base crawled; they have no metadata of their own and no original file, and their source is their URL.
Output
| Path | Value |
|---|---|
documents | The documents found, in order. |
count | How many there are — 0 when nothing matched. Branch on it with an If/Else. |
documents.0.documentName | The first document's filename (or a crawled page's title). |
documents.0.documentId | Its id in the knowledge base. |
documents.0.source | The citation: { kind: 'file', name }, or { kind: 'url', name, url } for a crawled page. |
documents.0.metadata | Its metadata, e.g. documents.0.metadata.invoice_number. |
documents.0.score | Its best score, 0–1. Empty on an exact lookup. |
documents.0.excerpt | The passage that scored best. Empty on an exact lookup. |
documents.0.file | Its original as an attachable file when Include files is on, otherwise empty. |
Files
With Include files on, each document's original is copied into the workflow's own files, so it is a file like any other on the canvas: Send Email's Attachments and AI Prompt's attachments take {{ upstream.<id>.output.documents.0.file }} unchanged. A retried step reuses the copy it already made rather than making another.
A file is skipped rather than failing the step — file is empty and fileSkippedReason says why:
too_large— over 20 MB, the most Send Email can attach to one message;unavailable— the original is no longer in storage.
A crawled web page has no original, so its file is always empty, with no reason.
Worked example: send an invoice copy on request
A customer writes "please send me a copy of INV-2026-0142". The workflow reads the invoice number out of the subject, looks it up in the Invoices knowledge base by its invoice_number metadata field, and replies with the invoice attached.
- An Email Trigger on your accounts-receivable mailbox, filtered to subjects containing
copy. - A Code node that pulls the invoice number out of the subject.
- Knowledge Search on
Invoices, with one filter —invoice_number={{ upstream.invoice-number.output.result.invoice }}— no query, and Include files on. - Send Email back to the sender, attaching
{{ upstream.find-invoice.output.documents.0.file }}.
Put an If/Else on {{ upstream.find-invoice.output.count }} before step 4 if you want a different reply when the invoice is not found.
name: Invoice copy on request
description: Replies to "copy of INV-…" emails with the invoice attached.
graph:
nodes:
- id: request
type: trigger_email
position: { x: 0, y: 0 }
data:
filterUnseen: true
filterSubjectContains: copy
downloadAttachments: false
postAction: markRead
pollMinutes: 5
- id: invoice-number
type: code
position: { x: 280, y: 0 }
data:
language: python
inputExpression: '{{ trigger.subject }}'
fileExpressions: []
timeoutSeconds: 10
code: |
import re
def main(input, files):
match = re.search(r"INV-\d{4}-\d+", input or "")
return {"invoice": match.group(0) if match else ""}
- id: find-invoice
type: knowledge_search
position: { x: 560, y: 0 }
data:
knowledgeBase: knowledgeBases.invoices
knowledgeBaseName: Invoices
query: ''
metadataFilters:
- id: by-number
field: invoice_number
value: '{{ upstream.invoice-number.output.result.invoice }}'
valueMode: expression
maxResults: 1
includeFiles: true
- id: reply
type: send_email
position: { x: 840, y: 0 }
data:
to: '{{ trigger.from.address }}'
subject: 'Re: {{ trigger.subject }}'
body: 'Please find {{ upstream.find-invoice.output.documents.0.documentName }} attached.'
bodyFormat: text
attachments: ['{{ upstream.find-invoice.output.documents.0.file }}']
edges:
- { id: request-to-number, source: request, target: invoice-number }
- { id: number-to-find, source: invoice-number, target: find-invoice }
- { id: find-to-reply, source: find-invoice, target: reply }Choosing the knowledge base
The picker lists the knowledge bases of this workflow's project, and the search runs in that project: a knowledge base of another project or organization is simply not found. A workflow that belongs to no project cannot use this node, because knowledge bases are always project-scoped. A knowledge base that is still being set up, or failed to, refuses with its status.
In a Package
The knowledge base reference travels, exactly as the Knowledge Base document trigger's does: it is converted to the knowledge base the Package itself declares (knowledgeBases.<name>), and the installed node searches the Package's own knowledge base. A node pointing at a knowledge base the Package does not declare, or at none, is refused at build time. Because a knowledge base lives in a project, a Package with this node installs into a project.
On the node:
- Travels:
knowledgeBaseName,query,metadataFilters,documentName,maxResults,includeFiles - Converted:
knowledgeBaseId→knowledgeBase(aknowledgeBases.<name>reference) - Never carried: none