# Agent Steps

Steps are the building blocks of an agent workflow. Each agent contains an ordered list of steps that execute sequentially — the output of one step becomes the input to the next. Steps can also reference outputs from any earlier step using substitution variables.

## Step Types

Agent steps are organized into seven categories, matching the step selector in the editor:

| Category                                                                | Steps                                                                                                                                             | Description                                                                                                                                           |
| ----------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **[Core Actions](https://seclai.com/docs/agent-steps/core)**                              | Text, Regex Replace, Filter, Content Input, Extract Content, Extract Data, Evaluate Step, Compact Input                                           | The fundamental building blocks: compose text, transform, extract, evaluate, compact                                                                  |
| **[AI & Generation](https://seclai.com/docs/agent-steps/ai-generation#prompt-call-step)** | Prompt Call, Generate Image, Generate Audio, Generate Video                                                                                       | Model-powered steps: call an LLM (optionally with tools and structured output), or generate images, audio, and video with dedicated generation models |
| **[Control](https://seclai.com/docs/agent-steps/control)**                                | Gate, Merge, Retry, Wait, For Each, If / Else, Switch, Human-in-the-Loop                                                                          | Branch, merge, iterate, retry, pause, and route the workflow                                                                                          |
| **[Content Actions](https://seclai.com/docs/agent-steps/content)**                        | Retrieval, Write Metadata, Write/Load Content Attachment, Load Content, Publish Content                                                           | Read, enrich, and publish knowledge base content                                                                                                      |
| **[Memory](https://seclai.com/docs/agent-steps/memory)**                                  | Write Memory, Append Chat Turn, Search Memory, Load Memory, Load Chat History                                                                     | Persistent agent memory across runs                                                                                                                   |
| **[Integration](https://seclai.com/docs/agent-steps/integration)**                        | Webhook Call, MCP Client Call, Web Fetch, Web Search, Write to S3, Send Email, Call Agent, List Cloud Drive Folder, Read / Write Cloud Drive File | Connect to external systems, APIs, cloud drives, and other agents                                                                                     |
| **[Output](https://seclai.com/docs/agent-steps/output)**                                  | Display Result, Item Value, Streaming Result                                                                                                      | Deliver the final result to users                                                                                                                     |

---

## Common Fields

Every step type shares these base fields:

| Field                  | Type   | Required | Description                                                                                                                                                                                                    |
| ---------------------- | ------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <code>step_type</code> | enum   | Yes      | The type of step (see sections below)                                                                                                                                                                          |
| <code>id</code>        | string | Auto     | A unique identifier for the step. Must match <code>{'^[a-zA-Z0-9_-]+$'}</code>. Auto-generated if not provided. Used to reference this step's output from other steps via <code>{'{{step.id.output}}'}</code>. |
| <code>name</code>      | string | No       | A human-readable label for the step, shown in the UI                                                                                                                                                           |
| <code>purpose</code>   | string | No       | A description of what this step does. Used by the AI assistant to understand context.                                                                                                                          |

### Child Steps (Composite Steps)

Most step types are **composite** — they can contain nested child steps. Child steps execute after their parent and receive the parent's output as input.

| Field                    | Type  | Default             | Description                                            |
| ------------------------ | ----- | ------------------- | ------------------------------------------------------ |
| <code>child_steps</code> | array | <code>{'[]'}</code> | Ordered list of child steps to execute after this step |

Nearly every step type is composite and supports <code>child_steps</code>. The only step types that do **not** support child steps are the terminal / non-composite ones: Display Result, Streaming Result, Item Value, and Retry.

**For Each** is different from other composite steps: its children are a **body template** that runs once per iteration of a runtime-resolved list, not a one-shot sequence. See the [For Each Step reference](https://seclai.com/docs/agent-steps/control#for-each-step) for iteration variables, slicing, parallelism, and trace behavior. Also note: <code>display_result</code>, <code>streaming_result</code>, <code>human_in_the_loop</code>, and <code>wait</code> are not allowed inside a For Each body — multiple iterations would produce ambiguous UI output (and, for the parking steps, ambiguous resumption).

**If / Else** and **Switch** also dispatch into named subtrees (<code>then_steps</code> / <code>else_steps</code> for If / Else; per-case <code>steps</code> plus <code>else_steps</code> for Switch). Each invocation runs <em>one</em> branch (or no branch when nothing matches and there is no else). After the chosen branch completes, the branch's output flows into the If / Else or Switch step's own <code>child_steps</code> — the post-branch continuation chain that always runs. <code>display_result</code> and <code>streaming_result</code> are <strong>not allowed</strong> inside any branch subtree (<code>then_steps</code> / <code>else_steps</code> / <code>cases[].steps</code>) because they are terminal output sinks; place them in the branching step's <code>child_steps</code> instead, with each branch ending in a content-producing step (<code>text</code> / <code>prompt_call</code>). See the [If / Else](https://seclai.com/docs/agent-steps/control#if-else-step) and [Switch](https://seclai.com/docs/agent-steps/control#switch-step) references for branch markers and trace behavior.

**Human-in-the-Loop** pauses the run, asks one or more humans to pick from a choice list, and resumes with the chosen outcome as the step's output. Downstream <code>if_else</code> / <code>switch</code> / <code>gate</code> steps route on <code>{`{{step.<hitl_id>.output}}`}</code>. See the [Human-in-the-Loop concept page](https://seclai.com/docs/human-in-the-loop) for the lifecycle / quorum / billing overview and the [Human-in-the-Loop Step reference](https://seclai.com/docs/agent-steps/control#human-in-the-loop-step) for the field-level configuration.

---

## String Substitutions

Most step fields support dynamic variable substitution using `{{placeholder}}` syntax. This lets steps reference agent input, other steps' outputs, metadata, and runtime values.

### Available Variables

| Variable                                        | Description                                                                               |
| ----------------------------------------------- | ----------------------------------------------------------------------------------------- |
| <code>{'{{input}}'}</code>                      | The current step's input (output of the previous step, or agent input for the first step) |
| <code>{'{{agent.input}}'}</code>                | The original agent input (always the initial input, regardless of step position)          |
| <code>{'{{agent.name}}'}</code>                 | The agent's name                                                                          |
| <code>{'{{agent.id}}'}</code>                   | The agent's unique ID                                                                     |
| <code>{'{{agent.run_id}}'}</code>               | The current run's unique ID                                                               |
| <code>{'{{agent_step.id}}'}</code>              | The current step's ID                                                                     |
| <code>{'{{agent_step.name}}'}</code>            | The current step's name                                                                   |
| <code>{'{{step.<step_id>.output}}'}</code>      | Output from a previous step with the given ID                                             |
| <code>{'{{step.<step_id>.input}}'}</code>       | Input to a previous step with the given ID                                                |
| <code>{'{{attachments}}'}</code>                | File attachments on the current step's input (see [File Attachments](#file-attachments))  |
| <code>{'{{attachments[N]}}'}</code>             | Single attachment at zero-based index `N`                                                 |
| <code>{'{{attachments[pattern]}}'}</code>       | Attachments whose filename matches the glob `pattern` (`*`, `?`)                          |
| <code>{'{{agent.attachments}}'}</code>          | All attachments from the original trigger uploads                                         |
| <code>{'{{agent.attachments[…]}}'}</code>       | Trigger uploads narrowed by index or filename pattern                                     |
| <code>{'{{step.<id>.attachments[…]}}'}</code>   | Attachments emitted by a prior step's output manifest (e.g. generated images)             |
| <code>{'{{metadata.<field>}}'}</code>           | A metadata field value from the trigger                                                   |
| <code>{'{{knowledge_base.name}}'}</code>        | Knowledge base name (in retrieval context)                                                |
| <code>{'{{knowledge_base.description}}'}</code> | Knowledge base description (in retrieval context)                                         |
| <code>{'{{organization.name}}'}</code>          | Organization name (falls back to user name)                                               |
| <code>{'{{organization.description}}'}</code>   | Organization description                                                                  |
| <code>{'{{user.name}}'}</code>                  | Name of the user who initiated the run                                                    |

### Date & Time Variables

Date and time variables accept an IANA timezone and an optional locale code. Currently, the locale code selects the format pattern (for example, ordering and separators), but month and day names are rendered in English by the backend implementation.

| Variable                                      | Format / Behavior                                          | Example Output                             |
| --------------------------------------------- | ---------------------------------------------------------- | ------------------------------------------ |
| <code>{'{{date UTC}}'}</code>                 | <code>YYYY-MM-DD</code>                                    | <code>2026-02-17</code>                    |
| <code>{'{{date America/New_York}}'}</code>    | <code>YYYY-MM-DD</code>                                    | <code>2026-02-17</code>                    |
| <code>{'{{date Europe/London en}}'}</code>    | Locale-specific date pattern (English month/day names)     | <code>February 17, 2026</code>             |
| <code>{'{{date Asia/Tokyo ja}}'}</code>       | Locale-specific date pattern (English month/day names)     | <code>February 17, 2026</code>             |
| <code>{'{{time UTC}}'}</code>                 | <code>HH:MM:SS</code>                                      | <code>14:30:00</code>                      |
| <code>{'{{time America/New_York}}'}</code>    | <code>HH:MM:SS</code>                                      | <code>09:30:00</code>                      |
| <code>{'{{datetime UTC}}'}</code>             | <code>YYYY-MM-DD HH:MM:SS</code>                           | <code>2026-02-17 14:30:00</code>           |
| <code>{'{{datetime Europe/Paris fr}}'}</code> | Locale-specific datetime pattern (English month/day names) | <code>February 17, 2026 03:30:00 PM</code> |

Function-style syntax is also supported: `{{datetime(UTC)}}` or `{{datetime(UTC, es)}}`. As with the inline syntax, the locale argument currently affects only the pattern, not the language of month or day names.

Unrecognized placeholders are left unchanged in the output, making it safe to include template syntax that should not be resolved.

---

## File Attachments

> For a high-level introduction with worked examples (uploads, generated images, per-file fan-out), see the [**Attachments guide**](https://seclai.com/docs/agent-attachments). The section below is the grammar and routing reference.

Agents accept **file attachments** alongside text input. When you trigger a run with one or more uploaded files (image, audio, video, PDF, document), each file flows through the agent in **two parallel channels**:

1. **Native binary** — capable LLMs (Claude, Gemini, Nova, GPT-4o-class) receive the raw bytes as a media content block.
2. **Extracted text** — every file is also OCR'd / transcribed; that text is what the LLM sees when the configured model is text-only, and what governance evaluators and the prompt scanner inspect.

A step "sees" attachments only when its template **declares the dependency**. This narrow-visibility rule prevents trigger uploads from leaking into intermediate steps that don't actually want them.

### How to reference attachments

Two declaration forms drive routing:

- **Implicit** — referencing <code>{'{{input}}'}</code>, <code>{'{{agent.input}}'}</code>, or <code>{'{{step.<id>.input|output}}'}</code> brings in **all** attachments carried on that source. Templates that already use these placeholders keep working unchanged.
- **Explicit** — the <code>{'{{attachments[…]}}'}</code> family **narrows** the set. When any explicit selector targets a source, the implicit "all" is replaced by the narrowed subset.

### Selector syntax

| Form                                               | Meaning                                                                                     |
| -------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| <code>{'{{attachments}}'}</code>                   | All attachments from the **current step's input** manifest                                  |
| <code>{'{{attachments[0]}}'}</code>                | The attachment at zero-based index `0` (out-of-range matches nothing)                       |
| <code>{'{{attachments[*.pdf]}}'}</code>            | All PDFs — filename glob via `fnmatch` (`*` matches any run of chars, `?` matches one)      |
| <code>{'{{attachments[invoice.pdf]}}'}</code>      | Literal filename — no globs ⇒ exact match                                                   |
| <code>{'{{agent.attachments}}'}</code>             | All attachments from the **trigger uploads** (always available regardless of step position) |
| <code>{'{{agent.attachments[*.png]}}'}</code>      | Trigger uploads narrowed by glob                                                            |
| <code>{'{{step.gen-image.attachments[0]}}'}</code> | The first attachment emitted by a prior step's output manifest (e.g. a generated image)     |

Multi-match is allowed — a glob that matches three files surfaces three media blocks. Zero-match is a no-op (logged at INFO).

In the resolved prompt text, each placeholder expands to <code>{'[attachment: <filename>]'}</code> markers so the LLM sees a textual anchor; the binary flows out-of-band as a native media block (or, when the model is text-only, as the OCR/transcript text under an `--- attachment text ---` separator).

### Per-step `attachments` config field

Steps that _consume_ a manifest (send_email, write_aws_s3_object, webhook_call, call_agent, publish_content, write_content_attachment, for_each, display_result, streaming_result, add_chat_turn) expose an **Attachments** config field — a list of <code>{'{{…}}'}</code> reference expressions that together narrow which manifest entries the step ingests. Each expression selects attachments from one source — this step's input (<code>{'{{attachments[…]}}'}</code>), the trigger uploads (<code>{'{{agent.attachments[…]}}'}</code>), or a specific upstream step's output files (<code>{'{{step.<id>.attachments[…]}}'}</code>) — by index, filename glob, or all:

```json
[
  "{{attachments[*.png]}}",
  "{{agent.attachments[invoice.pdf]}}",
  "{{step.gen-image.attachments[0]}}"
]
```

Expressions union their matches in list order; duplicates (same `(source, step_id, storage_key)`) are pruned with first-seen winning. On consumer steps (`send_email`, `write_aws_s3_object`, `webhook_call`, `call_agent`, `publish_content`, `write_content_attachment`, `for_each`), `null` / empty list defaults to "every attachment on the step's input manifest". On the result steps (`display_result`, `streaming_result`) and `add_chat_turn` the default is **text only** — they surface/persist attachments only when at least one reference is set; leaving the list empty attaches nothing.

The step editor's **Attachments** widget builds the reference list row-by-row with a live match-count preview. See [Attachments references field](https://seclai.com/docs/agent-steps/integration#attachment-references) for the full reference.

### Multi-modal routing

For each attachment a `prompt_call` step receives, the runtime picks one of three paths based on the configured model's capabilities:

1. **Native** — when the model declares support for the attachment's MIME (e.g. Claude Haiku 4.5 + `image/png`), the binary is sent as a media content block.
2. **OCR / transcript demotion** — when the model can't handle the MIME, the extracted-text counterpart is stitched onto the prompt body so the LLM still sees the content.
3. **Dropped** — no text counterpart available; an INFO log records the attachment, model, and reason.

Each prompt-call step emits a routing summary: `Attachment routing for step <id> on model <name>: considered=N native=N demoted=N dropped=N` — the forensic anchor when "the model didn't see my image" comes up.

See [Send a file](https://seclai.com/docs/agents#file-attachments) for the UI / MCP / API flow that uploads a file and attaches it to a run.

---

## Metadata Filters

The Retrieval step supports MongoDB-style metadata filters to narrow results based on document metadata fields. Any step that accepts a `filter` field uses this same syntax.

### Filter Operators

| Operator             | Description           | Example                                                      |
| -------------------- | --------------------- | ------------------------------------------------------------ |
| <code>$eq</code>     | Equals                | <code>{'{"category": {"$eq": "news"}}'}</code>               |
| <code>$ne</code>     | Not equals            | <code>{'{"status": {"$ne": "draft"}}'}</code>                |
| <code>$lt</code>     | Less than             | <code>{'{"score": {"$lt": 0.5}}'}</code>                     |
| <code>$lte</code>    | Less than or equal    | <code>{'{"priority": {"$lte": 3}}'}</code>                   |
| <code>$gt</code>     | Greater than          | <code>{'{"word_count": {"$gt": 100}}'}</code>                |
| <code>$gte</code>    | Greater than or equal | <code>{'{"published_date": {"$gte": "2026-01-01"}}'}</code>  |
| <code>$in</code>     | Value in list         | <code>{'{"category": {"$in": ["news", "blog"]}}'}</code>     |
| <code>$nin</code>    | Value not in list     | <code>{'{"status": {"$nin": ["draft", "archived"]}}'}</code> |
| <code>$exists</code> | Field exists          | <code>{'{"author": {"$exists": true}}'}</code>               |
| <code>$regex</code>  | Matches regex         | <code>{'{"title": {"$regex": "^Breaking"}}'}</code>          |
| <code>$not</code>    | Negation              | <code>{'{"status": {"$not": {"$eq": "draft"}}}'}</code>      |

### Logical Operators

| Operator          | Description                       |
| ----------------- | --------------------------------- |
| <code>$and</code> | All conditions must match         |
| <code>$or</code>  | At least one condition must match |

### Filter Examples

**Simple field match (implicit AND):**

```json
{
  "category": { "$eq": "technology" },
  "status": { "$ne": "draft" }
}
```

**Using $or:**

```json
{
  "$or": [
    { "category": { "$eq": "technology" } },
    { "category": { "$eq": "science" } }
  ]
}
```

**Complex nested filter:**

```json
{
  "$and": [
    { "published_date": { "$gte": "2026-01-01" } },
    {
      "$or": [
        { "category": { "$in": ["news", "analysis"] } },
        { "priority": { "$gt": 5 } }
      ]
    },
    { "author": { "$exists": true } }
  ]
}
```

**With substitution variables:**

```json
{
  "category": { "$eq": "{{metadata.category}}" },
  "published_date": { "$gte": "{{metadata.start_date}}" }
}
```

---

## Step Execution Order

Steps execute as a **directed acyclic graph (DAG)**. In the simplest case, steps run sequentially top-to-bottom. With child steps and parallel branches (via merge `wait_for`), the execution order follows these rules:

1. **Root steps** execute first, in order
2. **Child steps** execute after their parent completes, receiving the parent's output as input
3. **Merge steps** wait for every sibling step named in their `wait_for` list before executing
4. Awaited branches may continue running in parallel after the merge fires
5. A step only executes after all its dependencies have completed

*Figure: DAG execution with parallel branches: two processing paths run independently, and the merge waits for every branch tip named in its wait_for before combining results.*

---

## Step Caching

The **Prompt Call** and **Retrieval** steps support result caching to reduce costs and improve performance:

| Field                              | Description                                                                                                  |
| ---------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| <code>extended_caching_days</code> | Cache results for N days. Identical inputs return cached results.                                            |
| <code>cache_all_minutes</code>     | Cache ALL requests (regardless of input) for N minutes (1–60). Useful for time-insensitive batch operations. |

Caching is particularly useful for:

- Retrieval steps that run on the same knowledge base frequently
- Prompt calls with deterministic outputs (e.g., classification tasks with `temperature: 0`)
- Reducing credit usage for repeated operations

---

## AI Assistant

The AI assistant helps you configure steps by generating configurations from natural language descriptions. It understands your full agent workflow — including all steps and their relationships — and can suggest appropriate values.

**Supported step types:**

| Step Type            | What the AI Assistant Generates                                                                        |
| -------------------- | ------------------------------------------------------------------------------------------------------ |
| **Prompt Call**      | Model, prompts, temperature, tools, JSON template, formatting — full prompt call configuration         |
| **Retrieval**        | Knowledge base, query, filters, reranker, time range, content type — full retrieval configuration      |
| **Regex Replace**    | Regex patterns, substitutions, and comments for each rule                                              |
| **Gate**             | Conditions with targets, operators, values, match mode, and on_match                                   |
| **Merge**            | Output template with step references, and content type                                                 |
| **For Each**         | Input template, offset, limit, parallel, fail_fast, and fail_on_empty                                  |
| **If / Else**        | Conditions (target, operator, value), match mode, and optional input template for the discriminator    |
| **Switch**           | Discriminator template, value type, case names with single-value or list (`$in`) match patterns        |
| **Text**             | Template with substitution variables, and content type                                                 |
| **Call Agent**       | Target agent, pass-through settings for input/metadata, and content version                            |
| **Write Memory**     | Key, speaker, content, and metadata — full write memory configuration                                  |
| **Search Memory**    | Key, query, filters, time range, top_n — full search memory configuration                              |
| **Load Memory**      | Key, order, limit, and content type — full load memory configuration                                   |
| **Extract Data**     | Prompt, output format, output schema, model, temperature, max tokens — full extract data configuration |
| **Write Metadata**   | Metadata key, content, and content version — full write metadata configuration                         |
| **Write Attachment** | Attachment key, content type, content, indexed flag, and content version                               |
| **Load Attachment**  | Attachment key and content version                                                                     |
| **Load Content**     | Content version selection                                                                              |
| **Publish Content**  | Target source, identifier mode, content type, title, content, metadata, suppress triggers              |

To use the AI assistant, open a supported step and click the **AI Assistant** button. Describe what you want in natural language, and the assistant will generate or refine the configuration.

---

## Next Steps

- [Agent Triggers](https://seclai.com/docs/agent-triggers) — Configure when and how agents run
- [Agents Overview](https://seclai.com/docs/agents) — Back to the agents overview
- [Knowledge Bases](https://seclai.com/docs/knowledge-bases) — Connect data sources for retrieval steps
- [Content Sources](https://seclai.com/docs/content-sources) — Understand how content flows into knowledge bases
- [Memory Banks](https://seclai.com/docs/memory-banks) — Create and manage persistent memory for your agents
- [API Examples](https://seclai.com/docs/api-examples) — Code samples for working with agents

---
