Documentation

Content Action Steps

For common fields, string substitutions, metadata filters, and execution order, see the Agent Steps Overview.


Retrieval Step

Searches a knowledge base using semantic similarity (vector search) to find the most relevant content for a given query. This is the foundation of RAG (Retrieval-Augmented Generation) workflows.

Fields

FieldTypeRequiredDefaultDescription
knowledge_base_idUUIDYesThe knowledge base to search
querystringNonullThe search query. Supports substitutions. If not set, the step's input is used; if that is blank too, the run's uploaded file is the query (see below).
top_nintegerNo20Number of results to return (1–100). Prefilled from the knowledge base's default when you first pick it in the editor.
reranker_modelenumNonullReranker model to apply after initial retrieval. null inherits the knowledge base's reranker; "none" turns reranking off (see below)
top_kintegerNonullNumber of results to keep after reranking (1–100). Prefilled from the knowledge base. Ignored when reranking is off; still applied if Seclai skips one (below).
minimum_rerank_scorenumberNonullDrop matches the reranker scored below this (0.0–1.0). Only applies when a reranker runs; never filters on similarity. Prefilled from the knowledge base.
content_typeenumNoapplication/jsonOutput format: application/json or text/plain
filterobjectNonullMetadata filter to narrow results (MongoDB-style query, see Metadata Filters)
added_afterstringNonullOnly return content added after this date/time. Supports substitutions.
added_beforestringNonullOnly return content added before this date/time. Supports substitutions.
include_attachmentsbooleanNotrueWhen enabled, indexed content attachments are included in retrieval results alongside the main content body. Disable to limit results to original content only.
metadata_fieldsstring[]NonullMetadata keys to return on each match, read back from the metadata attached when the content was indexed. Omit to return none.

Searching with a photo

A knowledge base indexed with a multi-modal embedding model can be searched with a file rather than a sentence — send a phone photo and get back the page, product or diagram it matches. Point the run at the file and leave the retrieval step's query blank:

  • No query on the step, and
  • nothing feeding text into the step (the run's own input is empty, or the previous step produced nothing),
  • while the run carries an uploaded file that is a media type — an image, a video, an audio file or a PDF.

The file then becomes the query. With several media files on the run, Seclai picks the first one your knowledge base's embedding model can actually read — so attaching a voice memo alongside a photo, on an image knowledge base, still searches with the photo. When none of them qualifies, the first is used and the fallback below applies. Files that are not media — JSON, an archive, a spreadsheet, a text file — are never used as a query; their extracted text goes through the ordinary query path instead.

To search with each of several files, put the retrieval step inside a for_each loop over attachments: inside the loop body the query is that iteration's own file, not the run's first one.

Which media types are embedded natively is decided by the knowledge base's embedding model, not by this step. Amazon Nova 2 Multimodal reads images, video, audio and PDF; Cohere Embed v4 reads images. A type your embedder cannot read searches by the file's extracted text instead (below).

The same applies when content is indexed: on a capable embedder, an uploaded image, video, audio file or PDF is embedded whole, in addition to being indexed from its extracted text — so a photograph is findable by a photo even though it has no words in it.

Five things follow from this:

  • A file's own # {filename} heading does not count as input. Every upload's extracted text is placed under a # {filename} heading, so a run carrying nothing but a photo still has {{agent.input}} equal to "# photo.jpg". That heading is a label Seclai writes, not something you typed, so the retrieval step ignores it when deciding whether the step has a query — otherwise every photo-only run would silently search the knowledge base for its own filename. Headings of files that carry real extracted text still count, along with that text.
  • A blank query is only an error when there is nothing else to search with. If the step has no query, no input, and the run carries no media file, the run fails with "Query is required" — and the message names the attachment types that were present, so an attached .json that you expected to work says so rather than looking like a missing query.
  • Text and a file can be combined — but not every embedder uses both. Send input alongside the upload and the text is offered as the query with the file attached to it. Cohere Embed v4 embeds the two together; Amazon Nova 2 Multimodal takes exactly one input per call and uses the file, discarding the text. Don't rely on the wording to narrow a media search unless your knowledge base is on an embedder that combines them.
  • The knowledge base has to be able to read the file. If the sources are embedded with a model that can't read the file's type, Seclai does not embed it: it drops the file and searches on text alone, saying so in the trace — "Searched text-only — embedder does not support the attached query media". Which text depends on what you sent. Text of your own is used as-is, with the file set aside. With no text, the file's extracted-text counterpart (PDF text, an audio/video transcript) becomes the query instead — and a file with no counterpart either, such as a raw photo, leaves nothing to search with, so the step fails and tells you to re-index with a multi-modal embedder. See knowledge bases.
  • A media query is billed like a media chunk. Searching with a file costs one media unit at your embedder's rate for that modality — the same unit indexing charges per image — plus the tokens of any text you sent alongside it. Both appear as separate lines on the usage report.

The trace tells you which path a run took: "Searching knowledge base with the uploaded image" for a media-only query, "…with text and media" when both were sent, and plain "Searching knowledge base" for an ordinary text search. Reranking is skipped for a media-only query, since a reranker scores query text against match text, and there is no query text — see below.

The Test button in the step editor cannot preview this: a preview has no run and therefore no file, so it runs text retrieval only.

Two entrances to a knowledge base accept a file as the query, and they behave the same way because they are the same code path. This step is one. The other is knowledge base chat, where the paperclip attaches an image, audio file, video or PDF to your message: chat runs a retrieval step of its own and hands your uploads to it, so everything on this page applies there too. One difference — a chat message can never be empty, so a chat search is always text and file, never the file alone. Which of the two the embedder actually uses is the "text and a file can be combined" rule above.

The remaining ways in are text-only: the search_knowledge_base tool on a Prompt Call and the search MCP tools take a query string with nowhere to put a file. They still reach media — that is the indexing side, not the query side — and a media chunk they match is labelled with the same [image: …] citation described below.

Reranker Models

Rerankers improve result quality by re-scoring the initial retrieval results using a cross-encoder model. The retrieval first fetches top_n results, then the reranker re-scores them and returns the top top_k.

ModelDescription
Amazon Rerank v1AWS Bedrock-hosted reranker
Cohere Reranker v3.5High-quality reranker from Cohere

The step's reranker_model has three settings:

ValueMeaning
null (unset)Use whichever reranker the knowledge base is configured with — the default
"none"No reranking for this step, whatever the knowledge base is set to
A model IDRerank with that model

Reranking and media knowledge bases

Rerankers are text models: they score your query text against each match's chunk text. A knowledge base whose sources are embedded natively as images, audio or video stores those chunks with no text at all — the binary is the content — so there is nothing for a reranker to score.

Seclai handles this for you in three places, so you generally don't have to think about it:

  • A knowledge base created with every source using an embedding model that cannot handle text at all gets no reranker by default. (A multi-modal model — the default — indexes both, so it keeps one.)
  • A retrieval whose query is an image (or other media) with no text skips reranking — there would be no query text to score with either.
  • Within a mixed knowledge base, reranking only reorders the matches that actually carry text; native media matches keep the positions their similarity scores earned.

When Seclai skips a reranker your step or knowledge base had configured — including when the reranker itself fails — your top_k still bounds the results, and the run's trace says the reranking was skipped and why. Skipping does not quietly hand the next step top_n matches instead. A minimum_rerank_score keeps matches that carry no text for a reranker to score, since a threshold on rerank scores has nothing to say about a chunk that was never judged — otherwise it would quietly become a "text only" filter.

Set reranker_model to "none" when you want to be explicit — for example on a knowledge base that already holds a reranker from before its sources became media-only. That also skips the reranking charge, which the automatic skips above do too.

Output Format

When content_type is application/json (default), the output is a JSON object with a matches array of matching documents. Each object in matches contains:

FieldTypeDescription
content_titlestring | nullTitle of the matched content, capped at 256 characters and cut with a trailing beyond that, with line breaks flattened to spaces. Where it comes from depends on the source: a crawled page's own <title>, an RSS entry's title, or — for an upload — the title in the metadata you supplied, falling back to the filename. null when the content has no title.
source_typestringSource type (e.g. rss_feed, website, custom_index)
sourcestringName of the source connection
content_urlstringAbsolute URL to the content viewer in the Seclai app
external_urlstring (optional)Original source URL for website and RSS feed content. Omitted for content stores and other source types where not present.
contentstringThe matched text chunk. For a media match — a chunk that is an image, audio file, video or PDF — there is no text of its own: an asset extracted from a page or document carries its label instead (an image's alt text, a video's title or aria-label, a linked document's anchor text — capped at 512 characters and cut with a trailing ), and a match with no label carries the [image: name] citation, never an empty string (see below).
chunk_startintegerCharacter offset where the chunk starts in the full content. Both offsets are 0 on a media match, which has no position in a text body.
chunk_endintegerCharacter offset where the chunk ends in the full content
content_publish_datestring | nullOriginal publish date of the content, if available
page_numberinteger (optional)Page the match sat on, for an image extracted from a PDF. Omitted for text chunks and for formats without pages.
source_urlstring (optional)URL the matched asset itself was fetched from, for an image extracted from a web page. Omitted for uploaded documents.
source_mimestring (optional)MIME type of the matched binary, for a media chunk. Omitted for text chunks.
content_metadataobject (optional)The metadata keys named in metadata_fields, for those the matched content actually carries. Omitted entirely when metadata_fields is not set.

Example JSON output:

{
  "matches": [
    {
      "content_title": "Getting Started Guide",
      "source_type": "website",
      "source": "Documentation Site",
      "content_url": "https://app.seclai.com/app/abc123/contents/def456?start=0&end=1200",
      "external_url": "https://docs.example.com/getting-started",
      "content": "Welcome to the getting started guide...",
      "chunk_start": 0,
      "chunk_end": 1200,
      "content_publish_date": "2026-01-15T10:30:00Z"
    }
  ]
}

external_url is only present for website and rss_feed source types. For custom_index and other source types, the field is omitted.

page_number and source_url appear only when the match is an image (or other media) that was extracted out of a container document — a knowledge base with media indexing enabled. A PDF match carries page_number; an image pulled from a web page carries source_url pointing at the image itself, while external_url points at the page that contained it. Text chunks carry neither.

When content_type is text/plain, the output is the concatenated text of matching documents, suitable for direct use in prompts.

What a media match looks like

A knowledge base that indexes media returns matches whose content is a binary rather than text. Rather than handing the next step a blank content, Seclai fills that field one of two ways. An asset pulled out of a page or a document carries the label its author gave it — the alt text of an <img>, the title or aria-label of a <video>, the wording of the link a document was fetched from. Anything with no label of its own carries the same citation label the model is shown beside the file. That second case covers an uploaded file embedded whole, an image extracted from a PDF (which has no alt attribute to take), and equally a <video> or linked document whose own label was missing or blank:

{
  "matches": [
    {
      "content_title": "Assembly Manual",
      "source_type": "custom_index",
      "source": "Product Manuals",
      "content_url": "https://app.seclai.com/app/abc123/contents/def456?start=0&end=0",
      "content": "[image: page_7_image_0.png (Find page, page 7, match 1)]",
      "chunk_start": 0,
      "chunk_end": 0,
      "source_mime": "image/png",
      "page_number": 7
    }
  ]
}

source_mime is the field to branch on: it is present exactly when the match is a media chunk, and absent on every text chunk. Don't branch on an empty content — it is never empty on a media match, and a text chunk that produced no text looks identical.

A citation in content, as in the example above, is there so a text-only model still knows an image matched, and so a citation the model gives you can be traced back to a row in this output. It is built from the asset's own filename where it has one, falling back to the document title, and carries the step name, page and match position — see what the model sees. Don't parse it for meaning.

A label in content is the opposite: it is the caption the indexed page wrote for that asset, so it does carry meaning — but it is page content, not something Seclai composed, and it deserves the same trust you give the rest of a scraped page. It reaches you capped at 512 characters, with line breaks flattened to spaces and square brackets rewritten as parentheses, so a label can never be mistaken for one of the citations above.

The picture itself is not in this JSON. Matched media is added to the run's attachment pool, so a downstream LLM-call step (prompt_call, evaluate_step, extract_data) can receive the actual file and not just its label. Which reference you write decides whether it does:

The prompt referencesThe model receives
{{input}}the matches and their files — the ordinary retrieval → prompt chain, and the reason this usually needs no thought
{{attachments[*.png]}}exactly the files that reference selects
{{step.<id>.output}} onlythe matches as text only — no files
nothingno files

The third row is the one that surprises people. A named reference to a step's output reads that step's own output manifest, and a retrieval step emits JSON or plain text rather than a manifest — so there is nothing there to attach. This is the normal shape when a prompt pulls from a retrieval on another branch, or from two retrievals at once. Add an explicit {{attachments[…]}} alongside it and the files arrive: the pool is run-wide, so it reaches matches from any retrieval that has already run, not only the step feeding this one.

Whatever route the file takes, the receiving step's model has to be able to read it. If it cannot, the file is replaced by its extracted text — nothing at all for a raw photo — so pick a vision model for the step that consumes image matches.

Reading Metadata Back

Content indexed through POST /sources/{id}/upload can carry an arbitrary metadata object, and a retrieval step can already filter on it. To also read it back, name the keys in metadata_fields:

{ "metadata_fields": ["page", "section"] }

Each match then carries a content_metadata object holding just those keys, and only the ones that content actually has. Keys you do not name are never returned — the same metadata also holds bulky derived values (a podcast transcript, every section anchor on a docs page, a full RSS description) and internal bookkeeping, none of which belongs in an agent's prompt. For the same reason a single very large value is omitted even when named, and the object as a whole is capped — a retrieval step can return up to 100 matches into one prompt, so metadata that is individually reasonable can still be too much in aggregate. Keep metadata_fields to the handful of keys the agent actually reasons about.

Three things worth knowing:

  • The shape depends on content_type. With application/json (and application/xml, which emits JSON) each match carries a content_metadata object. With text/plain and text/html each match instead gains a single Metadata: page=7; section=Appendix B line — same keys, same order, rendered as key=value pairs joined with ; (non-string values render as JSON).
  • Filtering and reading are not the same set. filter matches against the metadata stored on the content itself, while what you read back is that merged with any metadata a write_metadata step has since attached, with the step's values winning. So a key written by write_metadata is readable but not filterable, and if both set the same key you filter on the original value while reading the newer one.
  • content_publish_date keeps its own top-level field even if you also name published_at.

Use Case Examples

Basic semantic search:

  • Knowledge base: Product documentation
  • Query: {{agent.input}}
  • Top N: 10
  • Content type: text/plain

The step searches for content semantically similar to the user's input and returns the top 10 matches as plain text.

Filtered retrieval with reranking:

  • Knowledge base: News articles
  • Query: {{agent.input}}
  • Filter: {"category": {"$eq": "{{metadata.category}}"}}
  • Top N: 50
  • Reranker: Cohere v3.5
  • Top K: 10
  • Added after: {{metadata.start_date}}

First retrieves 50 candidates matching the category filter added after the start date, then reranks them to select the 10 most relevant.

TriggerRetrievalfilter + rerankPrompt Callanswer questionStreaming Result
Figure 1.Filtered retrieval with reranking — the agent retrieves candidates matching a category filter, reranks them, and answers the question.

Time-bounded retrieval:

  • Knowledge base: RSS feed content
  • Query: Latest news about AI
  • Added after: 2026-02-10
  • Added before: 2026-02-17
  • Top N: 20

Retrieves only content added within the specified date range.

TriggerRetrievaldate range filterPrompt Callsummarize newsStreaming Result
Figure 2.Time-bounded retrieval — the agent searches only content added within a specific date range.
TriggerRetrievalsearch KBPrompt Callanswer with contextStreaming Result
Figure 3.Retrieval → Prompt Call → Streaming Result — a basic RAG pipeline that searches a knowledge base and answers the user's question.

AI Assistant

The retrieval step includes an AI assistant that can generate a complete configuration from a natural-language description. Click the AI Assistant button to open the assistant modal.

The assistant can set all retrieval fields:

  • Knowledge base — selects the most appropriate knowledge base when none is chosen, or suggests switching when another KB better matches the request
  • Query — writes query templates using substitution variables like {{input}} and {{step.<id>.output}}
  • Top N / Top K — sets retrieval and reranking counts based on use case
  • Reranker model — recommends a reranker when high relevance or multilingual content is needed
  • Time range — sets added_after / added_before filters using date templates
  • Metadata filter — builds MongoDB-style filter objects using known metadata fields from the knowledge base
  • Content type — selects the output format (JSON, plain text, HTML, XML)

The assistant is aware of the available knowledge bases (their names, descriptions, source counts, and detected metadata fields), the available reranker models, and the full agent workflow context. When no knowledge base is selected, the assistant will recommend one based on your request.


Write Metadata Step

Writes a value to the content's metadata by key. This enables agents to persist structured information — such as AI-generated classifications, tags, or scores — directly on the content record so that it can be used for retrieval filtering and gate conditions in future agent runs. For larger information like summaries, consider using Write Content Attachment and Read Content Attachment steps instead.

Fields

FieldTypeRequiredDefaultDescription
metadata_keystringYesThe key under which the value will be stored in the content metadata. Supports substitutions.
contentstringNonullThe value to write. If omitted, the step's input is used (equivalent to {{input}}). Supports substitutions.

Behavior

  • If the value is valid JSON, it is stored as a parsed JSON value (object, array, number, boolean, or null). Otherwise it is stored as a plain string.
  • The total serialized metadata payload must not exceed 10 KB. Performance may degrade with payloads above 2 KB.
  • Metadata written by this step is merged with content version metadata when content is retrieved. The content metadata takes precedence over content version metadata for duplicate keys.
  • The step requires a source_connection_content_version_id in the run metadata — this is automatically provided when the agent is triggered by a content event (content_added, content_updated, or content_added_or_updated).

Use Case Examples

Persist an AI-generated category for retrieval filtering:

Step 1: Extract Data — Classify the content into a category
Step 2: Write Metadata (metadata_key: "category")

A subsequent retrieval step can filter by {"category": {"$eq": "technology"}} to narrow results.

TriggerExtract Dataclassify contentWrite Metadatakey: category
Figure 4.Classify → Write Metadata — the agent classifies content and persists the category for retrieval filtering.

Store a sentiment score for gate conditions:

Step 1: Prompt Call — "Rate the sentiment of this content as positive, negative, or neutral"
Step 2: Write Metadata (metadata_key: "sentiment")

A gate step in a later agent can use metadata.sentiment with $eq to route content conditionally.

TriggerPrompt Callrate sentimentWrite Metadatakey: sentiment
Figure 5.Score → Write Metadata — the agent rates sentiment and stores it as metadata for gate conditions.

Write a JSON summary object:

Step 1: Extract Data — Extract key topics and summary as JSON
Step 2: Write Metadata (metadata_key: "analysis", content: "{{step.extract-data.output}}")

The JSON object is stored as structured metadata, and individual fields can be accessed in filters.

TriggerExtract Dataextract topicsWrite Metadatakey: analysis
Figure 6.Extract Data → Write Metadata (JSON) — the agent extracts structured topics and stores them as a JSON metadata object.

Write Content Attachment Step

Writes an attachment to content. The step input (or explicit content) is stored as a file-backed attachment under a specified key. This is ideal for persisting large outputs — such as full translations, analysis reports, or transformed content — that are too large for metadata but should be associated with the content record.

Fields

FieldTypeRequiredDefaultDescription
attachment_keystringYesA short identifier for the attachment (e.g. summary, translation). Must be unique per content version. Supports substitutions.
content_typeenumNotext/plainThe MIME type of the attachment: text/plain, text/html, application/json, or application/xml.
contentstringNonullThe content to write. If omitted, the step's input is used (equivalent to {{input}}). Supports substitutions.
indexedbooleanNofalseWhen enabled, the attachment text is indexed so that it appears in retrieval search results alongside the main content body.
attachmentslistNonullOptional list of {{…}} attachment references — when the step receives a multi-asset manifest input, the unioned matches must narrow to exactly one attachment (the step writes a single attachment per call). 0 or >1 match raises a validation error.

Behavior

  • Attachments are stored in file storage and linked to the content version.
  • Unlike metadata (which has a 10 KB limit), attachments can store large content.
  • When indexed is enabled, the attachment's text is chunked and embedded into the knowledge base's vector store, making it searchable via retrieval steps. The retrieval step's include_attachments field controls whether indexed attachments appear in results.
  • The step requires a source_connection_content_version_id in the run metadata.
  • Multi-asset manifest input (an upstream prompt step that emitted multiple images / files): attachments must narrow to one attachment. To write all attachments, wrap this step in a for_each with iterate_attachments=true.

Use Case Examples

Store a full translation alongside original content:

Step 1: Prompt Call — Translate the content to Spanish
Step 2: Write Content Attachment (attachment_key: "translation_es", content_type: text/plain)
TriggerPrompt Calltranslate to ESWrite Attachmenttranslation_es
Figure 7.Translate → Write Attachment — the agent translates content and stores the translation as an attachment for later recall.

Persist an indexed analysis that's searchable:

Step 1: Extract Data — Generate a detailed analysis report
Step 2: Write Content Attachment (attachment_key: "analysis", indexed: true)

The analysis is now searchable via retrieval, appearing alongside the original content in results.

TriggerExtract Dataanalysis reportWrite Attachmentindexed: true
Figure 8.Extract Data → Write Attachment (indexed) — the agent generates an analysis and stores it as a searchable indexed attachment.

Store structured data as a JSON attachment:

Step 1: Extract Data — Extract entities and relationships as JSON
Step 2: Write Content Attachment (attachment_key: "entities", content_type: application/json)
TriggerExtract Dataextract entitiesWrite AttachmentJSON entities
Figure 9.Extract → Write Attachment (JSON) — the agent extracts entities and persists them as a JSON attachment.

Load Content Step

Loads the full text body of a source document (content version) and returns it as the step output. For content-triggered agents, the triggering content is used automatically unless an explicit content version is selected.

Fields

FieldTypeRequiredDefaultDescription
content_version_idstringNoThe ID of the content version to load. If omitted, the triggering content version is used automatically.

Behavior

  • Returns the full text body of the content version as the step output.
  • For content-triggered agents, the triggering content version is used when content_version_id is not specified.
  • When an explicit content_version_id is provided, that content version is loaded regardless of trigger context.
  • The step requires a source_connection_content_version_id in the run metadata (provided automatically for content-triggered agents).

Use Case Examples

Load and summarize article content:

Step 1: Load Content
Step 2: Prompt Call — Summarize the following article: {{input}}
TriggerLoad ContentPrompt Callsummarize
Figure 10.Load Content → Summarize — the agent loads the full document and summarizes it.

Load content for multi-step analysis:

Step 1: Load Content
Step 2: Extract Data — Analyze the content for key themes and sentiment
Step 3: Extract JSON — Parse the structured analysis
TriggerLoad ContentExtract Datathemes + sentimentExtract JSON
Figure 11.Load Content → Extract Data → Extract — the agent loads a document, analyzes it, and parses the structured output.

Load Content Attachment Step

Loads a previously written attachment from content by key and returns its text content as the step output. Use this to recall persisted content — such as a previous analysis, translation, or extracted data — for use in subsequent processing steps.

Fields

FieldTypeRequiredDefaultDescription
attachment_keystringYesThe key of the attachment to load. Supports substitutions.

Behavior

  • Returns the text content of the attachment as the step output.
  • If the attachment does not exist for the given key, the step output is empty.
  • The step requires a source_connection_content_version_id in the run metadata.

Use Case Examples

Load a previous translation for comparison:

Step 1: Load Content Attachment (attachment_key: "translation_es")
Step 2: Prompt Call — Compare this translation with the original content
TriggerLoad Attachmenttranslation_esPrompt Callcompare
Figure 12.Load Attachment → Compare — the agent loads a stored translation and compares it with the original content.

Build on a previous analysis:

Step 1: Load Content Attachment (attachment_key: "analysis")
Step 2: Extract Data — Using the previous analysis, identify any changes
TriggerLoad AttachmentanalysisExtract Dataidentify changes
Figure 13.Load Attachment → Extract Data — the agent loads a previous analysis and identifies changes or updates.

Load Content Step vs Load Content Attachment vs Prompt-Call Tools

These three approaches all load document content, but serve different purposes:

ApproachWhat it loadsWhen to use it
Load Content stepThe full text body of a source documentYou need the entire document as a deterministic pipeline step — e.g. to feed it into a Prompt Call or Extract Data step for summarization or analysis.
Load Content Attachment stepA previously written attachment (by key)You need to recall data that was persisted by a prior agent run — e.g. a cached translation, extracted entities, or a previous analysis.
load_content prompt-call toolThe full text of a source document, invoked by the AI modelYou want the model to decide at runtime whether and which document to load. The model autonomously calls this tool during a Prompt Call step when it determines it needs document content.

The key distinction: the Load Content step runs unconditionally as part of the pipeline, while the load_content prompt-call tool is invoked by the AI model on demand during a Prompt Call step. If you always need the document, use the step. If the model should decide whether to fetch it, enable the tool.


Combining Metadata and Attachments

Metadata and attachments serve complementary purposes:

FeatureMetadata (Write Metadata)Attachments (Write/Load)
Size limit10 KB totalNo practical limit
FilterableYes (retrieval filters, gates)No (but can be indexed for search)
SearchableNoYes (when indexed: true)
Best forTags, scores, categoriesFull text, reports, translations
Access method{{metadata.<key>}}Load Content Attachment step

Example: Extract Data-driven content enrichment pipeline:

Step 1: Extract Data — Classify content and extract summary
Step 2: Extract JSON — Parse the JSON output
  Step 3: Write Metadata (metadata_key: "category", content: "{{step.extract-json.output.category}}")
  Step 4: Write Metadata (metadata_key: "sentiment", content: "{{step.extract-json.output.sentiment}}")
  Step 5: Write Content Attachment (attachment_key: "detailed_summary", indexed: true)

This pipeline: classifies content (filterable via metadata), records sentiment (usable in gate conditions), and persists a searchable detailed summary (retrievable via attachment indexing).

TriggerExtract Dataclassify + summarizeExtract JSONWrite Metadatakey: categoryWrite Metadatakey: sentimentWrite Attachmentindexed summary
Figure 14.Combined enrichment pipeline — classify, score sentiment, and persist a searchable summary in one agent run.

Publish Content Step

Publishes content to a Content Store source connection. The content goes through the standard indexing pipeline and becomes searchable via retrieval. Use this to let agents create or update knowledge base content — for example, storing generated summaries, reports, or transformed data.

Fields

FieldTypeRequiredDefaultDescription
source_connection_idstringNonullThe ID of the target content store source. RSS feeds, websites, and other source types are not supported. For content-triggered agents, leave blank to publish back to the triggering content's source. Supports substitutions.
content_identifier_modeenumNoautoauto — creates new content each run. custom — uses a custom identifier so the same item is updated on subsequent runs.
content_identifierstringNonullA custom content identifier (only used when mode is custom). Content with the same identifier is treated as the same item. Supports substitutions.
if_existsenumNonew_versionWhat to do when content with the same identifier already exists: new_version (update) or skip (leave unchanged).
content_typeenumNotext/plainThe MIME type of the content: text/plain, text/html, application/json, or application/xml.
titlestringNonullAn optional title for the published content. Supports substitutions.
contentstringNonullThe content to publish. If omitted, defaults to the step input. Supports substitutions.
metadataobjectNonullOptional key-value metadata to attach to the published content. String values that look like substitution placeholders are resolved.
suppress_triggersbooleanNotrueWhen enabled, the published content will not fire downstream content triggers. Enabled by default to prevent infinite loops.
attachmentslistNonullOptional list of {{…}} attachment references — when the step receives a multi-asset manifest input, the unioned matches must narrow to exactly one attachment to publish as the content body. 0 or >1 match raises a validation error.

Behavior

  • Only Content Store sources are supported as targets. Other source types (RSS feeds, websites) will be rejected.
  • In auto mode, every run creates a new content item with a unique identifier.
  • In custom mode, publishing with the same identifier updates the existing item (creating a new version) or skips it, depending on the if_exists setting.
  • The suppress_triggers flag defaults to true to prevent infinite loops when a content-triggered agent publishes back to a monitored source. Only disable this if you are certain the target is not monitored by content triggers.
  • Works with any agent type. For content-triggered agents, the source connection and content version can be inherited from the trigger context.
  • Multi-asset manifest input: attachments must narrow to one attachment. To publish all attachments as separate content items, wrap this step in a for_each with iterate_attachments=true.

Use Case Examples

Generate and store article summaries:

Step 1: Load Content
Step 2: Prompt Call — Summarize the article
Step 3: Publish Content (source_connection_id: "<custom-index-id>", title: "Summary: {{metadata.title}}")
TriggerLoad ContentPrompt CallsummarizePublish Contentto content store
Figure 15.Load → Summarize → Publish — the agent loads an article, summarizes it, and publishes the summary to a content store.

Overwrite a daily report:

Step 1: Retrieval — Search for today's data
Step 2: Prompt Call — Generate daily report
Step 3: Publish Content (content_identifier_mode: custom, content_identifier: "daily-report", if_exists: new_version)
TriggerRetrievaltoday's dataPrompt Callgenerate reportPublish Contentcustom ID, update
Figure 16.Retrieval → Report → Publish (update) — the agent gathers data, generates a report, and overwrites the existing daily report.

Content-triggered enrichment (publish back to same source):

Step 1: Load Content
Step 2: Extract Data — Analyze and enrich the content
Step 3: Publish Content (suppress_triggers: true)
TriggerLoad ContentExtract Dataenrich contentPublish Contentsuppress triggers
Figure 17.Enrichment loop — the agent loads content, enriches it, and publishes back to the same source with triggers suppressed.

Related pages:

  • Content Sources — Configure the sources that feed your knowledge bases
  • Contents — Inspect, replace, and delete indexed content items via API
  • Knowledge Bases — Organize sources into searchable collections

Attachments references field (Publish / Write Content Attachment)

New to attachments? See the Attachments guide for the high-level model and examples.

Both Publish Content and Write Content Attachment write a single content body per call. When the step receives a multi-asset manifest input (e.g. from an upstream prompt step that emitted multiple generated images), the attachments field must narrow the manifest to exactly one attachment. To write every attachment, wrap the step in a for_each with iterate_attachments=true to fan out.

The attachments field is a list of {{…}} attachment reference expressions, the same grammar used by the Webhook, S3, Send Email, and Call Agent steps:

["{{attachments[0]}}"]
ConfigurationBehaviour
null / [] with non-manifest inputWrites the step input as-is
null / [] with manifest inputValidation error — the step needs explicit references when input is a manifest
["{{attachments[0]}}"]Writes the first input attachment
["{{attachments[invoice.pdf]}}"]Writes the attachment named exactly invoice.pdf
["{{attachments[*.pdf]}}"]Resolves to all PDFs — must match exactly one, else validation error
["{{step.gen-image.attachments[0]}}"]Writes the first image emitted by a prior step's output manifest

For writing all attachments as separate content items, wrap the step in a for_each with iterate_attachments=true — the loop iterates one body execution per attachment.

← Back to Agent Steps Overview