# Agents

AI agents in Seclai are multi-step LLM pipelines that chain model calls, retrieval, validation, and actions together — with built-in retries, evaluation, governance, observability, and cost tracking at every step.

## What are Agents?

Agents are AI-powered workflows that can:

- **Call AI models** (GPT, Claude, Gemini, and 90+ others) to generate responses
- **Search knowledge bases** using semantic similarity and metadata filters
- **Transform and extract** data from JSON, XML, and HTML
- **Integrate with external systems** via webhooks and APIs
- **Send notifications** via email
- **Run automatically** on schedules or when content changes
- **Store results** in AWS S3 or display them directly
- **Remember context** across conversations using persistent [memory banks](https://seclai.com/docs/memory-banks)
- **Branch and merge** parallel processing paths with gates, joins, and combinators

Each agent is composed of ordered **steps** that execute as a directed graph, with outputs from one step feeding into the next. This creates powerful, flexible workflows for content processing and automation.

## Agent Design Considerations

Before building an agent, it helps to decide where on the spectrum between **deterministic pipeline** and **autonomous agent** your use case falls. You can mix both styles in a single agent, and most production agents do.

### Deterministic Pipelines

A deterministic pipeline uses explicit steps that run in a fixed order with known inputs and outputs. You define exactly what happens at each stage — which URL to fetch, which knowledge base to search, what template to apply. The model's job is limited to generating text from a well-scoped prompt.

**Example — Daily news digest:**

```
Trigger: Schedule (daily at 8 AM)
Step 1: Web Fetch → fetch https://example.com/feed
Step 2: Prompt Call → "Summarize the top 3 stories"
Step 3: Send Email → deliver to team@example.com
```

*Figure: Deterministic pipeline — every step runs in a fixed order with known inputs. The model only generates text; all data flow is controlled by explicit steps.*

**When to choose this approach:**

- The inputs and data sources are known at design time
- Predictability matters more than flexibility — you want the same behavior every run
- You need to minimize token cost — the model only processes what you give it
- You want easy debugging — each step's input and output is visible in the trace
- The agent must work with models that don't support function calling

### Autonomous (Agentic) Behavior

An autonomous agent uses [tool-enabled prompt calls](https://seclai.com/docs/tools#tool-enabled-steps) where the model decides what actions to take. The model can search the web, query knowledge bases, read documents, write to memory, and iterate — all driven by the prompt context rather than a fixed script.

**Example — Research assistant:**

```
Trigger: Dynamic Input
Step 1: Prompt Call (with web tools + KB tools) → "Research the user's
        question. Search our internal docs and the web. Cite sources."
Step 2: Display Result → show the answer
```

*Figure: Autonomous agent — the model decides which tools to call, how many times, and in what order. A single tool-enabled prompt call replaces a chain of explicit steps.*

**When to choose this approach:**

- The task is open-ended — you don't know which sources or queries will be needed
- The model needs to adapt its strategy based on what it finds (refine searches, follow links, try alternative queries)
- You want fewer steps to maintain — a single tool-enabled prompt call can replace a chain of explicit steps

**Tradeoffs to consider:**

- Higher token cost — tool calls and their results consume tokens across multiple rounds
- Less predictable — the model may skip tool calls or use them in unexpected ways
- Harder to debug — when the model makes a poor tool-calling decision, it can be less obvious than a failed explicit step
- Requires function-calling models — see [Models](https://seclai.com/docs/models) for compatible options

### Combining Both Approaches

Most production agents benefit from combining both styles. Use deterministic steps for the parts of the pipeline that are predictable (fetching a specific URL, loading a known document, writing to a fixed memory key) and tool-enabled prompt calls for the parts that benefit from model autonomy (analysis, synthesis, follow-up research).

**Example — Content analysis with structured output:**

```
Trigger: Content Added
Step 1: Load Content → load the new document (deterministic)
Step 2: Prompt Call (with memory tools) → "Analyze this document.
        Check memory for prior context. Save key findings." (autonomous)
Step 3: Extract Content → extract the JSON analysis (deterministic)
Step 4: Send Email → deliver the structured report (deterministic)
```

*Figure: Hybrid agent — deterministic steps (Load Content, Extract, Send Email) handle the predictable parts, while a tool-enabled Prompt Call adapts its behavior based on the document.*

Steps 1, 3, and 4 always run the same way. Step 2 adapts based on the document content and what it finds in memory.

For a detailed comparison of tools versus explicit steps — including a decision table for common scenarios — see [Tools: Tools vs. Explicit Steps](https://seclai.com/docs/tools#tools-vs-explicit-steps).

## Agent Definition

An agent definition includes:

| Field           | Type   | Required | Description                                                   |
| --------------- | ------ | -------- | ------------------------------------------------------------- |
| **Name**        | string | Yes      | A descriptive identifier for the agent                        |
| **Description** | string | No       | Details about the agent's purpose                             |
| **Tags**        | array  | No       | Labels for organization and filtering                         |
| **Steps**       | array  | Yes      | The ordered list of processing steps (must have at least one) |

Agent definitions are versioned via **branches** and **changes**, similar to version control. Each change captures a snapshot of the agent's full configuration including all steps and their settings.

## Model Lifecycle Defaults

Agents can define lifecycle defaults for model-based steps:

- `prompt_model_auto_upgrade_strategy`: `none`, `early_adopter`, `middle_of_road`, or `cautious_adopter`
- `prompt_model_auto_rollback_enabled`: enables rollback after configured failure signals
- `prompt_model_auto_rollback_triggers`: subset of `agent_eval_fail`, `governance_flag`, `governance_block`, `agent_run_failed`

These defaults apply to **Prompt Call** and **Insight** steps unless a step explicitly sets per-step overrides.

Precedence is:

1. Step-level override
2. Agent-level default
3. System default

For field-level details and examples, see [Agent Steps: Model Lifecycle Controls](https://seclai.com/docs/agent-steps/core#model-lifecycle-controls).

Configure these settings via the API or MCP:

```bash
curl -X PUT "https://api.seclai.com/api/agents/$AGENT_ID" \
  -H "X-API-Key: $SECLAI_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "prompt_model_auto_upgrade_strategy": "cautious_adopter",
    "prompt_model_auto_rollback_enabled": true,
    "prompt_model_auto_rollback_triggers": ["agent_eval_fail", "governance_block"]
  }'
```

See [Model Lifecycle: Automatic Model Upgrades](https://seclai.com/docs/model-lifecycle#automatic-model-upgrades) for strategy descriptions and rollback behaviour.

## Triggers

Triggers define **when** an agent runs and **what input** it receives. An agent can have multiple triggers, each with its own type, configuration, and optional schedule.

| Trigger Type                 | Input Source                | Runs When                                                                        |
| ---------------------------- | --------------------------- | -------------------------------------------------------------------------------- |
| **Dynamic Input**            | Provided at runtime         | On-demand via API or UI                                                          |
| **Template Input**           | Pre-defined template        | On-demand or on schedule                                                         |
| **Content Added**            | From new content            | Automatically when content is added to a [knowledge base](https://seclai.com/docs/knowledge-bases) |
| **Content Updated**          | From updated content        | Automatically when content changes in a [knowledge base](https://seclai.com/docs/knowledge-bases)  |
| **Content Added or Updated** | From new or changed content | Automatically on any content change                                              |

All triggers support **metadata** — key-value pairs accessible in steps via `{{metadata.field_name}}`.

For complete trigger documentation, including scheduling, metadata examples, and use cases, see [Agent Triggers](https://seclai.com/docs/agent-triggers).

## Steps

Agents execute a series of steps in order. Each step type serves a specific purpose:

| Step Type                                                                               | Description                                                                                                                  |
| --------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| [**Prompt Call**](https://seclai.com/docs/agent-steps/core#prompt-call-step)                              | Call an AI model (90+ supported) to generate responses, with optional tools and structured JSON output                       |
| [**Retrieval**](https://seclai.com/docs/agent-steps/content#retrieval-step)                               | Search a knowledge base using semantic similarity with optional reranking and metadata filters                               |
| [**Text**](https://seclai.com/docs/agent-steps/core#text-step)                                            | Produce output from a template with variable substitutions                                                                   |
| [**Transform**](https://seclai.com/docs/agent-steps/core#transform-step)                                  | Apply regex-based substitution rules to transform text                                                                       |
| [**Gate**](https://seclai.com/docs/agent-steps/control#gate-step)                                         | Evaluate conditions to control execution flow (continue or stop)                                                             |
| [**Combinator**](https://seclai.com/docs/agent-steps/core#combinator-step)                                | Merge outputs from parallel branches into a single result                                                                    |
| [**Join**](https://seclai.com/docs/agent-steps/control#join-step)                                         | Connect a parallel branch to a combinator                                                                                    |
| [**Display Result**](https://seclai.com/docs/agent-steps/output#display-result-step)                      | Mark output as the final visible result                                                                                      |
| [**Streaming Result**](https://seclai.com/docs/agent-steps/output#streaming-result-step)                  | Stream LLM output tokens in real-time via SSE ([Streaming Guide](https://seclai.com/docs/agent-streaming))                                     |
| [**Extract Content**](https://seclai.com/docs/agent-steps/core#extract-content-step)                      | Extract JSON/XML/HTML from text and optionally convert format                                                                |
| [**Webhook Call**](https://seclai.com/docs/agent-steps/integration#webhook-call-step)                     | Make HTTP requests to external APIs                                                                                          |
| [**Write AWS S3 Object**](https://seclai.com/docs/agent-steps/integration#write-s3-step)                  | Save content to an S3 bucket                                                                                                 |
| [**Send Email**](https://seclai.com/docs/agent-steps/integration#send-email-step)                         | Send email notifications                                                                                                     |
| [**Call Agent**](https://seclai.com/docs/agent-steps/integration#call-agent-step)                         | Call another agent as a sub-workflow                                                                                         |
| [**Insight**](https://seclai.com/docs/agent-steps/core#insight-step)                                      | Use an AI model with progressive-disclosure tools to analyze large input content                                             |
| [**Evaluate Step**](https://seclai.com/docs/agent-steps/core#evaluate-step)                               | Score a previous step output and emit structured JSON (<code>score</code>, <code>passed</code>, <code>pass_threshold</code>) |
| [**Write Memory**](https://seclai.com/docs/agent-steps/memory#memory-write-step)                          | Write content to a memory bank, partitioned by key (with optional speaker for conversation banks)                            |
| [**Search Memory**](https://seclai.com/docs/agent-steps/memory#memory-search-step)                        | Search a memory bank using vector similarity, scoped by key                                                                  |
| [**Load Memory**](https://seclai.com/docs/agent-steps/memory#memory-load-step)                            | Load all memory entries for a key in chronological or reverse-chronological order                                            |
| [**Write Metadata**](https://seclai.com/docs/agent-steps/content#write-metadata-step)                     | Write a key-value pair to the agent run's metadata                                                                           |
| [**Write Content Attachment**](https://seclai.com/docs/agent-steps/content#write-content-attachment-step) | Write large content as an attachment to a content version                                                                    |
| [**Load Content Attachment**](https://seclai.com/docs/agent-steps/content#load-content-attachment-step)   | Load a previously written content attachment                                                                                 |
| [**Retry**](https://seclai.com/docs/agent-steps/control#retry-step)                                       | Re-execute from an ancestor step up to a maximum number of times                                                             |
| [**For Each**](https://seclai.com/docs/agent-steps/control#for-each-step)                                 | Iterate a body of steps over a runtime-resolved list, producing one step run per item                                        |
| [**If / Else**](https://seclai.com/docs/agent-steps/control#if-else-step)                                 | Evaluate gate-style conditions and dispatch to a `then` branch on match or an optional `else` branch                         |
| [**Switch**](https://seclai.com/docs/agent-steps/control#switch-step)                                     | Dispatch on a single discriminator value to one of N named cases (with optional `else` default arm)                          |
| [**Human-in-the-Loop**](https://seclai.com/docs/agent-steps/control#human-in-the-loop-step)               | Pause the run for a human approve/deny/custom-choice decision before continuing                                              |

Steps support **string substitutions** using `{{placeholder}}` syntax to reference agent input, other steps' outputs, metadata, dates/times, and more.

*Figure: A simple agent execution flow: Trigger → Generate Text → Display Result.*

For complete step documentation, including all fields, AI assistant support, and detailed examples, see [Agent Steps](https://seclai.com/docs/agent-steps).

## Creating an Agent

### Via the App

1. Navigate to **Agents** in your account
2. Click **Create Agent**
3. Configure the agent:
   - **Name** — Give it a descriptive name
   - **Description** — Optional details about its purpose
   - **Trigger Type** — Select when it should run
   - **Knowledge Base** — (Optional) Connect to a knowledge base
   - **Template** — Start with blank or use a retrieval example
4. Add and configure steps
5. Save and test

### Via the API

Create agents programmatically using the API. See [API Examples](https://seclai.com/docs/api-examples) for code samples.

## Running Agents

### Manual Execution

Run agents on demand via the UI or API:

**Via the UI:**

1. Open the agent
2. Click **Run** on the trigger
3. For dynamic input triggers: enter your input text and optional metadata
4. For template input triggers: the template renders automatically

**Via the API:**

```bash
curl -X POST https://api.seclai.com/agents/{agent_id}/runs \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "input": "What are the latest AI trends?",
    "metadata": {
      "user_id": "123",
      "source": "api"
    }
  }'
```

### Automatic Execution

Agents run automatically when:

- **Scheduled triggers** fire at the configured time(s) — see [Schedules](https://seclai.com/docs/agent-triggers#schedules)
- **Content triggers** detect changes in the connected [knowledge base](https://seclai.com/docs/knowledge-bases) — see [Content Triggers](https://seclai.com/docs/agent-triggers#content-added)

## File Attachments

Agents with a `dynamic_input` trigger accept **file attachments** alongside text input. Supported types: text, PDF, DOCX/PPTX/XLSX, images (PNG, JPEG, GIF, BMP, TIFF, WebP, SVG), audio (MP3, WAV, M4A, FLAC, OGG), video (MP4, MOV, AVI). **Size limit: 200 MB per file.**

SVG uploads are sanitized at write time (and re-sanitized at every serve) to strip `<script>`, event-handler attributes (`onclick`, `onload`, ...), `<foreignObject>`, `javascript:` / `data:text/...` URIs in `href` / `xlink:href`, and CSS `expression()` / `behavior:` payloads. Animation elements (`<animate>`, `<set>`) are unconditionally dropped because they can target `attributeName="onclick"` and inject a script payload. The cleaned SVG retains shapes, gradients, filters, text, and inline raster `data:image/...` references; everything else is removed. SVG bytes that can't be parsed as XML at all are rejected with an upload error.

Each upload flows through two parallel channels:

- **Native binary** — multi-modal LLMs (Claude, Gemini, Nova, GPT-4o-class) see the raw bytes
- **Extracted text** — every file is OCR'd / transcribed (audio/video via AssemblyAI, documents via MarkItDown / Tesseract). Text-only models see this content; the prompt scanner and governance evaluators always see this content regardless of which channel the model uses

Audio originals are preserved past transcription so audio-capable models (Gemini 2.5/3, GPT-5 audio) read them directly; the [agent-input-binary janitor](https://seclai.com/docs/troubleshooting) sweeps blobs past `AGENT_INPUT_BINARY_RETENTION_DAYS` (default 30 days).

### Via the UI

In the **Run Agent** modal, click the file-upload affordance below the input text area. Drop one or more files; previews appear inline. The "Send" button enables once the uploads finish processing.

Text input typed into the same modal is still available as <code>{'{{agent.input}}'}</code> / <code>{'{{input}}'}</code>; uploaded files reach the steps via the attachment placeholders described below.

### Via MCP

Two-step flow:

1. **Upload** — `upload_agent_input(agent_id, filename, content_b64, content_type)` stages the file and returns `{upload_id, status, ...}`. Text and document files come back `status="ready"` immediately; audio/video are submitted to AssemblyAI and start as `status="processing"` — poll `get_agent_input_upload_status(upload_id)` until ready.
2. **Run** — `run_agent(agent_id, input_upload_id=...)` for a single file, or `run_agent(agent_id, input_upload_ids=[...])` for multiple.

### Via the API

```bash
# 1. Upload the file
UPLOAD_ID=$(curl -X POST https://api.seclai.com/agents/{agent_id}/upload-input \
  -H "X-API-Key: YOUR_API_KEY" \
  -F "file=@./invoice.pdf" \
  -F "content_type=application/pdf" \
  | jq -r .id)

# 2. (Audio/video only) poll until status=ready
curl https://api.seclai.com/agents/{agent_id}/input-uploads/$UPLOAD_ID \
  -H "X-API-Key: YOUR_API_KEY"

# 3. Trigger the run referencing the upload
curl -X POST https://api.seclai.com/agents/{agent_id}/runs \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"input_upload_id\": \"$UPLOAD_ID\"}"
```

For multiple files, pass `input_upload_ids: [...]` instead.

### Referencing attachments in steps

By default, steps see attachments only when their template **declares the dependency**:

- Referencing <code>{'{{input}}'}</code>, <code>{'{{agent.input}}'}</code>, or <code>{'{{step.<id>.input|output}}'}</code> brings in **all** attachments carried on that source (implicit visibility).
- Use <code>{'{{attachments[…]}}'}</code> selectors **inside text fields** (prompt templates, email HTML body, S3 object key, …) to **narrow** the set — by index (<code>{'{{attachments[0]}}'}</code>), filename glob (<code>{'{{attachments[*.pdf]}}'}</code>), or exact filename (<code>{'{{attachments[invoice.pdf]}}'}</code>).

Steps that consume manifest input (send_email, write_aws_s3_object, webhook_call, call_agent, publish_content, write_content_attachment, for_each, display_result, streaming_result) expose a dedicated **Attachment Rules** config field — a **structured list** of selection rules. Each rule combines a `source` (`input`, `agent`, or `step` + `step_id`) with a `kind` (`all`, `index`, or `pattern`) and emits the matching attachments. Multiple rules **union** their matches in rule order, deduplicated by `(source, step_id, storage_key)`:

```json
{
  "attachment_rules": [
    { "source": "input", "kind": "pattern", "pattern": "*.pdf" },
    { "source": "input", "kind": "pattern", "pattern": "*.csv" }
  ]
}
```

LLM-call steps (prompt_call, call_agent, evaluate_step, extract_data) route attachments via inline `{{attachments[…]}}` placeholders in their prompt fields (no rule-list field — the placeholder grammar is what triggers media-block routing). Legacy single-string `attachments` selectors on consumer steps are **auto-migrated** to a single-entry rule list on the next save.

See [File Attachments](https://seclai.com/docs/agent-steps#file-attachments) in the Agent Steps reference for the full grammar, multi-modal routing rules, and per-step behaviour.

### Upload & run validation

Saved agents carry a static **attachment-reference contract** computed from every consumer step's templates. The contract drives three gates so an upload mismatch surfaces as a clear error rather than a silently ignored file:

- **Editor save** — placing `{{attachments[…]}}` on a non-consumer step type (text, gate, transform, …) or `{{step.<id>.attachments[…]}}` against a step that doesn't produce a multi-asset manifest (only `prompt_call` and `call_agent` do) is rejected at save time with a field-level error pointing at the offending placeholder.
- **Upload time** — when an agent's definition declares **no** attachment references at all, `upload_agent_input` (MCP) and `POST /agents/{id}/upload-input` (REST) reject with `agent_does_not_accept_uploads` / HTTP 400 so a user doesn't waste cycles uploading files that will never be consumed. The Run Agent modal hides the "Attach files" toggle entirely in this case.
- **Run time** — when the agent declares attachment references, the batch passed to `run_agent` must satisfy them: every **exact filename** (`{{attachments[invoice.pdf]}}`) must appear in the batch; the highest **declared index** (`{{attachments[2]}}`) must be in range; every **glob pattern** (`{{attachments[*.pdf]}}`) must match at least one filename. Unmet selectors fail the run with HTTP 400 and a message listing the missing requirements.

When the agent does require uploads, the Run Agent modal renders a hint block listing the expected filenames, the minimum file count for indexed references, and any required patterns so users know what to upload before they hit the file picker.

Attached files are also scanned in parallel: every upload's extracted text flows through the prompt-injection scanner on its own thread, and any single unsafe attachment (a jailbreak embedded in an uploaded PDF, for example) flips the run's input scan to `UNSAFE` and blocks downstream LLM-calling steps — closing the previous text-only blind spot on the safety layer. The Input Scan pseudo-step in the run trace surfaces an `attachments_scanned` count alongside the main verdict so you can confirm at a glance that uploads were screened. **Fail-closed semantics:** if a per-attachment scanner call fails (transient outage, network error), the run is marked `UNSAFE` rather than silently skipping the unscanned file.

### Discovering an agent's attachment contract

Both MCP and the external API expose the agent's static attachment-reference contract so you can stage uploads that satisfy it on the first try:

- **MCP**: `get_agent_attachment_references(agent_id)` — returns `{requires_uploads, agent: {kinds, indexes_max, exact_names, patterns}}`. Call this before `upload_agent_input` / `run_agent` so you know whether the agent accepts files at all, and what specific filenames/indexes/patterns the templates reference.
- **REST**: `GET /agents/{agent_id}/attachment-references` — same shape and semantics.

The frontend Run Agent modal calls this automatically on open and uses the response to hide the "Attach files" toggle (when `requires_uploads === false`) or render expectation hints (filename list, minimum file count, glob patterns).

### Downloading attachments emitted by a run

Attachments produced by a step (image-generation, transcripts, generated PDFs) are accessible to external consumers via an authenticated download endpoint:

```
GET /api/v2/agent-runs/{run_id}/attachments/{attachment_id}
```

- **Auth:** dual-auth — send `X-API-Key` or OAuth Bearer (same pattern as the rest of `/api`).
- **Scoping:** the calling account must own `run_id`; cross-account access returns 403.
- **`attachment_id`:** the URL-safe base64-encoded `storage_key` of the attachment as it appears in a step output manifest within the run (the encoder is shared by the webhook + email payload builders, so the URLs they emit round-trip cleanly).
- **MIME handling:** inline-safe MIMEs (`image/*`, `audio/*`, `video/*`, `application/pdf`, `text/plain`, `application/vnd.seclai.manifest+json`) are served with their declared type; everything else gets forced to `application/octet-stream` with an attachment `Content-Disposition` to prevent stored-XSS.
- **404 vs 403:** the server validates that the storage_key is referenced by some step output in the run before responding, so guessing keys cross-tenant returns 404 (not 403).

Users who need **public** URLs (e.g. embedding generated images in a public webpage) must download via this endpoint and re-host elsewhere — Seclai does not emit unauthenticated download URLs.

### Webhook attachment delivery modes

`webhook_call` steps with `payload_shape = json_with_urls` (the default for multi-asset outputs) emit per-attachment metadata as `{name, mime, url, bytes}`. The URL shape is controlled by `attachment_url_mode`:

- **`presigned_s3`** (default) — 1-hour presigned AWS S3 URLs. Receivers don't need a Seclai API key; bandwidth bypasses the Seclai API; URL expires after 1 hour and is not revocable. Best for low-trust short-lived consumers.
- **`seclai_auth`** — URLs hit `/api/v2/agent-runs/{run_id}/attachments/{attachment_id}` on the Seclai API. Receivers MUST send X-API-Key or a Bearer token belonging to the same account as the agent. No expiry; revocable by rotating the API key; every fetch is logged. Best for audited integrations or receivers that already hold a key.

`json_with_base64` and `multipart_form` payload shapes are unaffected — they inline the bytes directly.

### Email attachment delivery (CID + URL fallback)

`send_email` step HTML bodies can reference attachments inline:

```html
<img src="{{attachments[0]}}" alt="generated chart" />
<img src="{{step.gen.attachments[*.png]}}" />
```

When a `<img src="{{attachments[…]}}">` reference resolves to an image attachment, the step handler:

1. **CID-embeds** the image (under a 10 MB per-image cap and a 20 MB cumulative inline budget per email) by converting the placeholder to a `data:image/X;base64,…` URI which the email sender's existing MIME pipeline turns into a `multipart/related` inline part. Recipients see the image inline regardless of their email client's "block remote images" setting.
2. **URL-rewrites** images that exceed either cap (or attachments that aren't images) to an `https://api.seclai.com/api/v2/agent-runs/…/attachments/…` link — recipients click → authenticate → see the file. Email recipients are always Seclai org members so the OAuth flow is available to them.
3. **Drops** any single attachment that exceeds the 25 MB SES per-message envelope entirely (the email would be rejected by SES anyway) and logs an `EMAIL_ATTACHMENT_DROPPED_OVERSIZED` activity event.

Each CID embed and URL rewrite emits its own activity event (`EMAIL_ATTACHMENT_CID_EMBEDDED` / `EMAIL_ATTACHMENT_URL_REWRITTEN`) with the count and total bytes so reports can split inline-vs-URL usage.

### Surfacing attachments in display_result / streaming_result

`display_result` and `streaming_result` are also attachment-consumer steps: setting `attachment_rules` on either makes the step emit a multi-asset manifest (`application/vnd.seclai.manifest+json`) instead of plain text. The trace viewer's `StepOutputManifest` component renders the attachments inline via the existing media-preview machinery, so generated images / audio / PDFs surface at the agent's terminal exactly the way they surface on a `prompt_call` output today.

Streaming token SSE events are unaffected — clients still see text streaming live; the manifest only appears on the trace as the step's persisted output after the stream completes.

### Atomic attachment-rule mutations (MCP + REST)

The full-definition `update_agent_definition` (PUT the entire JSON) is fine for editor saves, but for one-rule changes — especially from an LLM that doesn't want to round-trip the whole agent JSON — a focused set of CRUD endpoints exists:

- **REST**:
  - `GET    /api/agents/{agent_id}/steps/{step_id}/attachment-rules` — list current rules + change_id.
  - `POST   /api/agents/{agent_id}/steps/{step_id}/attachment-rules` — `{rule, position?}` — insert at `position` (or append).
  - `PUT    /api/agents/{agent_id}/steps/{step_id}/attachment-rules/{rule_index}` — `{rule}` — replace by index.
  - `DELETE /api/agents/{agent_id}/steps/{step_id}/attachment-rules/{rule_index}` — remove by index; later rules shift down.
  - `PUT    /api/agents/{agent_id}/steps/{step_id}/attachment-rules/order` — `{new_order: [...]}` — bulk reorder via a permutation list.
  - `POST   /api/agents/{agent_id}/steps/{step_id}/attachment-rules/preview` — `{rules?, sample_attachment_names?}` — dry-run validation + per-rule match resolution against a sample; never mutates.
- **MCP**: corresponding tools `list_agent_step_attachment_rules`, `add_agent_step_attachment_rule`, `update_agent_step_attachment_rule`, `remove_agent_step_attachment_rule`, `reorder_agent_step_attachment_rules`, `preview_agent_step_attachment_rules`.

Each mutating call routes through the same validator the editor's full PUT uses (Pydantic schema → step-graph checks → placeholder + rule-reference checks), so no surface can bypass validation by routing around the editor. Validation failure surfaces in the REST 422 detail as a structured array — `{rule_index, field, code, message, suggestion}` per failing rule plus an `other_validation_errors` bucket for cross-step issues; MCP tools return the same shape inline in the response.

Every successful mutation creates a new `AgentBranchChange` row (one per call) so the audit trail stays linear and reverts are surgical.

### S3 step path + key split

`write_aws_s3_object` splits its destination into two fields:

- **`object_path`** (optional) — folder prefix. May reference run-level substitutions (`{{agent.run_id}}`, `{{datetime UTC}}`) but **not** per-attachment placeholders (`{{name}}`, `{{index}}`, `{{ext}}`) — those belong on `object_key` so each attachment writes to a distinct filename within the same path.
- **`object_key`** — the filename portion. For multi-asset prompt outputs include `{{name}}`, `{{index}}`, or `{{ext}}` so each attachment writes to a distinct key. Without a placeholder, a multi-asset input is rejected at runtime to prevent silent overwrites.

The final S3 key is `{object_path}/{object_key}` (joined with a single `/`) when `object_path` is set, else just `{object_key}`. Legacy `object_key` strings containing a `/` are auto-migrated on save: the last `/` separates path from key.

## Monitoring Runs

### Run Status

Each agent run has a status:

| Status         | Description                     |
| -------------- | ------------------------------- |
| **Pending**    | Queued for execution            |
| **Processing** | Currently running               |
| **Completed**  | Finished successfully           |
| **Failed**     | Error occurred during execution |

### Step Runs

Each step within a run tracks:

- **Status** — Pending, processing, completed, or failed
- **Input and output** — The data flowing in and out of the step
- **Error messages** — Details if the step failed
- **Duration** — How long the step took to execute
- **Credit usage** — Credits consumed by the step (for AI model calls)

### Agent Traces

Agent traces are per-run execution records that show what happened during a run from start to finish. They include status, step-level inputs and outputs, errors, timing, and credit usage.

You can find them here:

- **UI:** Account → Agents → [Agent Name] → Traces tab
- **API:** `GET /agents/{agent_id}/runs`

The primary purpose of traces is operational visibility. Use them to debug failures, validate outputs, diagnose performance bottlenecks, and monitor reliability over time. Traces also support searching across past runs so you can quickly find specific results.

For complete details and best practices, see [Agent Traces](https://seclai.com/docs/agent-traces).

## Agent Warnings

The system analyzes your agent definition and provides warnings for:

- Missing required metadata fields referenced in substitutions
- Undefined step references (e.g., `{{step.nonexistent.output}}`)
- Invalid model selections
- Configuration issues that could cause runtime failures

Warnings appear in the agent editor and help you fix issues before deployment.

## Validation

Agents are validated at **save time** (not just at execution) so a definition
cannot be persisted if it has any of:

- Missing required fields (e.g., `model` on `prompt_call`, `knowledge_base_id`
  on `retrieval`) or invalid enum values (e.g., `convert_to: "json"` on
  `extract_content` — only `html`, `markdown`, or `plain_text` are valid).
- Step references (`{{step.X.output}}`) that point at parallel siblings or
  descendants. Only **strict ancestors** and **join-reachable** branches
  are addressable.
- Aggregating `for_each` loops with no downstream consumer of
  `{{step.<for_each_id>.output}}` — the aggregated array would silently
  drop.
- Memory steps targeting a memory bank of the wrong type (`add_chat_turn`
  on a general bank, `add_memory` on a conversation bank, etc.).
- `chat_history` input on a `prompt_call` whose parent isn't an
  `add_chat_turn` or `load_chat_history`.
- `streaming_result` on an agent without a user-input trigger
  (`dynamic_input` or `template_input`).
- Cycles in the step graph.
- Step nesting deeper than the cap or text fields beyond the per-field
  size limit.

Validation errors are returned with **per-field paths** (e.g.
`agent.child_steps[0].prompt_call.model`) so the editor can point the user
straight to the offending field. The same checks run on the visual editor
save path, the bulk-update API, the patch API, and the AI-assistant-driven
writes from `generate_agent_steps`.

## Alerts

Configure alerts to be notified when agent runs match specific criteria. Alerts can monitor run outcomes and trigger notifications based on conditions you define.

## Exporting Agents

Export an agent's full definition as a portable JSON snapshot. The export
includes the complete step workflow, trigger configuration with schedules,
alert configs, and a **dependency manifest** that resolves every referenced
external entity (knowledge bases, memory banks, source connections, other
agents, users) to its human-readable name.

**What's included in the export:**

- `export_version` — schema version for forward compatibility
- `exported_at` — ISO-8601 timestamp
- `software_version` — application version that produced this export
- `agent` — name, description, schema version, full definition, timestamps
- `trigger` — trigger type, input template, and schedules (if any)
- `alert_configs` — alert type, thresholds, and recipients (if any)
- `evaluation_criteria` — evaluation settings per step (if any)
- `governance_policies` — agent-scoped governance policies (if any)
- `dependencies` — grouped lists of knowledge bases, memory banks, source connections, agents, and users referenced by the definition

**How to export:**

- **UI (detail page):** Click the **Export** button in the agent header.
- **UI (list page):** Open the agent's action menu (⋮) and select **Export**.
- **Public API:** `GET /api/agents/{agent_id}/export` with your API key.
- **MCP:** Use the `export_agent` tool with the agent ID.

The export is a read-only snapshot — it does not modify the agent.

> **Round-trip:** A previously exported agent JSON can be re-imported as-is via the UI's **Import** button or the create/update API endpoints — see [Importing Agents](#importing-agents) below.

> **Other export types:** You can also export an agent's [trace history](https://seclai.com/docs/agent-traces#exporting) and [evaluation results](https://seclai.com/docs/agent-evaluations#exporting) as bulk data files in JSON, JSONL, or CSV. See [Export Formats](https://seclai.com/docs/export-formats) for all available export types.

## Importing Agents

Re-import a previously exported agent definition to create a new agent (or apply the workflow + metadata to an existing one). The import accepts the **same JSON shape** that `Export` produces, so a round-trip is supported out of the box.

**Forward compatibility:** The import schema is intentionally lenient — unknown top-level fields (e.g. ones added by a newer software version) are silently ignored, and most optional fields can be omitted. The minimum payload is a top-level `agent` object containing a `definition`; everything else is optional. This means an export from a newer Seclai deployment usually still imports cleanly into an older one.

**What gets applied on create:**

- `agent.definition` — the full step workflow becomes the new agent's initial workflow.
- `agent.*` metadata — `description`, evaluation tier/mode, retry settings, sampling, and prompt-model auto-upgrade/rollback fields fill in any value you didn't override on the create call.
- `trigger` — input template, content type, metadata, and any schedules attached to the trigger.
- `evaluation_criteria` — only criteria whose `step_id` exists in the imported workflow.
- `alert_configs` — alert type, thresholds, recipients. Recipients are resolved by user UUID first; if that fails (importing across accounts) the export's `dependencies.users` block is used to fall back to email-based lookup. Recipients that match neither are skipped with a server-side warning.
- `governance_policies` — agent-scoped policies. Knowledge-base associations are matched by **name** in the target account; unknown names are skipped.

**What does _not_ happen on update:** alert configs, evaluation criteria, and governance policies are intentionally **not** touched on update — they have their own dedicated endpoints to avoid silently destroying state. Updates do still:

- Apply agent metadata fields the request didn't set explicitly.
- Replace the workflow from `agent.definition`. The previous version is preserved in history; nothing is overwritten in place.

**Validation errors** include line/column references against a canonical pretty-printed echo of your payload, plus a path and a human-readable message — Pydantic's raw vocabulary is intentionally never exposed. The 422 response body looks like:

```json
{
  "detail": {
    "error": "invalid_agent_definition",
    "message": "Invalid agent definition payload.",
    "errors": [
      {
        "line": 7,
        "column": 19,
        "path": "agent.definition.child_steps[0].step_type",
        "message": "Field 'step_type' has an invalid value (expected one of [...])."
      }
    ],
    "source": "<canonical pretty-printed JSON of the payload>"
  }
}
```

(The shown object is the contents of `detail`. The HTTPException raised by the FastAPI endpoint wraps it under that key on the wire.)

The `line` and `column` are 1-indexed and reference the canonical pretty-printed rendering returned in `source`, so they're stable regardless of how the original JSON was formatted.

**Workflow validation errors** (cycles in the step graph, invalid prompt-call tooling, depth/text-length caps, etc.) raised at save time return a similar 422 under `detail`: `{error, message, validation_errors:[{field, message, code, line, column}], source}`. The shape mirrors parse-time errors so callers can render line/column-precise feedback the same way: when the request supplied an `agent_definition`, the router anchors each entry's `line`/`column` against the canonical pretty-printed echo (returned in `source`); when no import payload was supplied, `line`/`column` default to `1` and `source` is the empty string. Each entry also carries `field` (a dotted path like `agent.definition.child_steps[0].id`) and an optional stable `code`. The UI's import modal handles both envelopes uniformly via `frontend/app/utils/agent-import-error.ts::normaliseImportFailureDetail`.

**Cross-account portability:** When importing an export from a different account, workflow entity references (knowledge bases, memory banks, source connections, sub-agents called by `call_agent` steps) won't match by UUID. The preview endpoint scans the workflow and returns `unresolved_refs` — one entry per unmatched reference, each with the available `alternatives` of the same type in the target account. Pass `entity_remap: {source_uuid: target_uuid}` on the create/update call to substitute them before save:

```json
{
  "name": "Imported",
  "trigger_type": "dynamic_input",
  "agent_definition": { "agent": { "definition": { ... } } },
  "entity_remap": {
    "11111111-1111-1111-1111-111111111111": "<target-account-kb-uuid>"
  }
}
```

When a category has zero alternatives in the target account (e.g. you're importing a workflow that uses a knowledge base but the target account has no KBs at all), the UI blocks the import until you create the missing resource. Create the resource first, then re-import. The API will still accept the request if you omit the remap, but the agent will fail at runtime when it tries to dereference the original UUID.

Alert recipients still fall back to **email-based lookup** via the export's `dependencies.users` manifest. Governance KB attachments still match by **name**. In both cases, items that can't resolve are skipped and surfaced via `import_warnings` (see below).

**Skip log on commit (`import_warnings`):** every successful create/update response that involved an `agent_definition` payload includes an `import_warnings` array. Each entry has `{category, message, details}` and explains an item that was dropped or had a default substituted. Categories: `schedule`, `evaluation_criteria`, `alert_config`, `alert_recipient`, `governance_policy`, `governance_kb_link`, `solution_link`. Empty array means nothing was skipped; field omitted entirely means no import happened on that call.

**How to import:**

- **UI (list page):** Click **Import** in the agents list header, pick the file, optionally rename. If the workflow references resources that don't exist in this account, the modal shows a picker for each unresolved reference; choose substitutes (or cancel and create the missing resources first), then click **Import as new agent**. After commit, any items that were skipped are listed in a yellow notice that you must dismiss before the modal closes.
- **UI (existing agent):** Click **Import** on the agent detail page. Update only replaces the workflow + agent metadata — alert configs, evaluation criteria, governance policies, schedules, and solution links from the imported file are **not** applied. The modal warns you when the imported file carries any of those sections.
- **Public API (preview):** `POST /api/agents/preview-import` with `{"agent_definition": {...}}` returns the summary plus `unresolved_refs`. Use this to drive a remap UI before committing.
- **Public API (commit):** `POST /api/agents` (or `PUT /api/agents/{id}`) with `{"name": "...", "trigger_type": "...", "agent_definition": {...}, "entity_remap": {...}}`. The response includes `import_warnings`.
- **MCP:** Use the `preview_agent_import` tool to validate and discover unresolved refs, then `create_agent` (or `update_agent`) with the same `agent_definition` plus `entity_remap`.

## AI Assistants

Seclai provides AI assistants that help you build and refine agent workflows from natural language. All assistants follow a **propose-then-accept** pattern: you describe what you want, the assistant generates a plan or configuration, and you review and accept or decline it. The assistant never makes changes without your approval.

### Workflow Generator

Generates a complete multi-step agent workflow from a natural language description.

**Access:** Open the agent editor and click **Generate with AI**, or describe what you want when creating a new agent.

**What it generates:**

- The full step graph with correct step types, ordering, and branching
- Prompt templates and system templates for each prompt call
- Retrieval configurations (knowledge base selection, top_n, search queries)
- Memory steps wired to the correct memory banks
- Gate conditions, extract content queries, webhook configurations
- Model selections appropriate for each step's complexity

**Example prompts:**

- "Build a customer support chatbot that uses my Product Information KB and remembers user preferences"
- "Create an agent that monitors news, summarizes daily, and emails the digest to team@example.com"
- "Build an agent with parallel branches — one searches the KB, one fetches a web page — then combines both results"

**Tips for best results:**

- **Describe the outcome, not the implementation.** Say "answer product questions from my docs" instead of "create a retrieval step then a prompt_call step."
- **Name resources by name.** Use "my Product Information knowledge base" instead of UUIDs — the assistant resolves names automatically.
- **Describe the full pipeline in one prompt.** The assistant handles branching, ordering, and wiring when it can see the whole picture.
- **State constraints up front.** "Use Haiku for classification and Sonnet for the summary to keep costs low" works better than correcting after.

See [Prompt Best Practices](https://seclai.com/docs/prompt-best-practices) for guidance on the prompt templates the generator creates.

### Step Configurator

Refines the configuration of an individual step after the workflow has been generated or manually created. Each step type has a tailored assistant.

**Access:** Open any step in the agent editor and click the **AI Assistant** button (sparkle icon).

**Supported step types:**

| Step Type         | Configuration Help                                           |
| ----------------- | ------------------------------------------------------------ |
| Prompt Call       | System/prompt templates, model selection, temperature, tools |
| Gate              | Condition expressions, pass/fail routing                     |
| Extract Content   | JSONPath/CSS/XPath queries, format selection                 |
| Text Transform    | Regex patterns, replacement strings, templates               |
| Retrieval         | Search queries, top_n tuning, knowledge base selection       |
| Send Email        | Recipient lists, subject/body templates                      |
| Memory steps      | Key expressions, speaker fields, query strings               |
| Call Agent        | Agent selection, input mapping                               |
| Context Compactor | Summary instructions, token limits                           |

**Example prompts:**

- "Change this prompt call to return JSON with fields: topic, severity, and summary"
- "Set up a gate that passes when the sentiment score is above 0.7"
- "Write a regex that extracts email addresses from the input"

**MCP tools:** `generate_agent_steps` (workflow generator), `generate_step_config` (step configurator). See [MCP Server](https://seclai.com/docs/mcp).

For general best practices on getting the most from all AI assistants, see [AI Assistant Best Practices](https://seclai.com/docs/ai-assistants#best-practices).

## Example Workflows

### RAG Chat Bot

A customer support bot that answers questions using your documentation:

```
Trigger: Dynamic Input
Step 1: Retrieval — Search documentation knowledge base
Step 2: Prompt Call — Generate answer using retrieved context
Step 3: Display Result — Show the answer
```

*Figure: RAG Chat Bot — a retrieval step searches the documentation knowledge base, a prompt call generates an answer using the retrieved context, and the display result shows it to the user.*

### Content Monitor

Monitor RSS feeds and send email summaries of new articles:

```
Trigger: Content Added (on news knowledge base)
Step 1: Prompt Call — Summarize the new article
Step 2: Gate — Only continue if article is relevant
Step 3: Send Email — Deliver the summary
```

*Figure: Content Monitor — new articles trigger a summary, a gate filters for relevance, and matching articles are emailed to the team.*

### Scheduled Report

Generate a daily analysis report from your knowledge base:

```
Trigger: Template Input (daily schedule at 8 AM)
  Template: "Generate daily report for {{date America/New_York}}"
Step 1: Retrieval — Fetch recent content
Step 2: Prompt Call — Analyze and generate report
Step 3: Write AWS S3 Object — Store the report
Step 4: Send Email — Deliver the report
```

*Figure: Scheduled Report — a daily trigger fetches recent content, generates a report, then stores it in S3 and emails it in parallel.*

### Parallel Analysis Pipeline

Run multiple analyses in parallel and combine results:

```
Trigger: Dynamic Input
Step 1: Retrieval — Search knowledge base
  ├── Step 2a: Prompt Call (summarize) → Join → Combinator
  └── Step 2b: Prompt Call (extract entities) → Join → Combinator
Step 3: Combinator — Merge summary and entities
Step 4: Write AWS S3 Object — Store combined result
Step 5: Display Result — Show to user
```

*Figure: Parallel Analysis Pipeline — retrieval feeds two parallel prompt calls (summarize and extract), a combinator merges the results, then the output is stored and displayed.*

## Next Steps

- [Agent Traces](https://seclai.com/docs/agent-traces) — Understand run-level observability, debugging, and monitoring
- [Agent Triggers](https://seclai.com/docs/agent-triggers) — Deep dive into trigger types, schedules, and metadata
- [Agent Steps](https://seclai.com/docs/agent-steps) — Comprehensive reference for all step types
- [Knowledge Bases](https://seclai.com/docs/knowledge-bases) — Connect data sources to agents
- [Memory Banks](https://seclai.com/docs/memory-banks) — Give agents persistent memory across conversations
- [Content Sources](https://seclai.com/docs/content-sources) — Learn about content sources for knowledge bases
- [API Examples](https://seclai.com/docs/api-examples) — Code samples for agent automation
