# Vertesia Documentation

Generated from the docs MDX source at request time.

## Contents

- [Vertesia Documentation: Engineering Reliable Generative AI for the Enterprise](https://docs.vertesiahq.com/llms/index.md)
- [Configuration](https://docs.vertesiahq.com/llms/agent-runner/configuration.md)
- [Custom Tools](https://docs.vertesiahq.com/llms/agent-runner/custom-tools.md)
- [Email Integration](https://docs.vertesiahq.com/llms/agent-runner/email-integration.md)
- [Execution Model](https://docs.vertesiahq.com/llms/agent-runner/execution-model.md)
- [Agent Runner - Getting Started](https://docs.vertesiahq.com/llms/agent-runner/getting-started.md)
- [Identity & Access](https://docs.vertesiahq.com/llms/agent-runner/identity.md)
- [MCP Tools](https://docs.vertesiahq.com/llms/agent-runner/mcp-tools.md)
- [Agent Message System](https://docs.vertesiahq.com/llms/agent-runner/messaging.md)
- [Overview](https://docs.vertesiahq.com/llms/agent-runner/overview.md)
- [SDK Integration](https://docs.vertesiahq.com/llms/agent-runner/sdk.md)
- [Skills](https://docs.vertesiahq.com/llms/agent-runner/skills.md)
- [How Skills Work](https://docs.vertesiahq.com/llms/agent-runner/skills-model.md)
- [Built-in Tools](https://docs.vertesiahq.com/llms/agent-runner/tools.md)
- [Workstreams](https://docs.vertesiahq.com/llms/agent-runner/workstreams.md)
- [Introduction](https://docs.vertesiahq.com/llms/api/introduction.md)
- [Installed Application Settings](https://docs.vertesiahq.com/llms/apps/installation-settings.md)
- [What are Applications?](https://docs.vertesiahq.com/llms/apps/overview.md)
- [MCP Servers](https://docs.vertesiahq.com/llms/apps/tool-collections.md)
- [Building a UI Plugin](https://docs.vertesiahq.com/llms/apps/ui.md)
- [Vertesia CLI](https://docs.vertesiahq.com/llms/cli.md)
- [Concepts](https://docs.vertesiahq.com/llms/concepts.md)
- [Embeddings Configuration](https://docs.vertesiahq.com/llms/content/embeddings.md)
- [Markdown Export Guide](https://docs.vertesiahq.com/llms/content/markdown-export.md)
- [Content Indexing Overview](https://docs.vertesiahq.com/llms/content/overview.md)
- [Search Configuration](https://docs.vertesiahq.com/llms/content/search.md)
- [Dashboards](https://docs.vertesiahq.com/llms/data-platform/dashboards.md)
- [Getting Started](https://docs.vertesiahq.com/llms/data-platform/getting-started.md)
- [Overview](https://docs.vertesiahq.com/llms/data-platform/overview.md)
- [Skills Reference](https://docs.vertesiahq.com/llms/data-platform/skills.md)
- [Tools Reference](https://docs.vertesiahq.com/llms/data-platform/tools.md)
- [Configuration](https://docs.vertesiahq.com/llms/environments.md)
- [AWS Bedrock](https://docs.vertesiahq.com/llms/environments/aws.md)
- [GCP Vertex AI](https://docs.vertesiahq.com/llms/environments/gcp.md)
- [Model Deprecation](https://docs.vertesiahq.com/llms/environments/model-deprecation.md)
- [OpenAI](https://docs.vertesiahq.com/llms/environments/openai.md)
- [Errors](https://docs.vertesiahq.com/llms/errors.md)
- [Agent nodes](https://docs.vertesiahq.com/llms/processes/agent-nodes.md)
- [Authoring processes](https://docs.vertesiahq.com/llms/processes/authoring.md)
- [The Process Model](https://docs.vertesiahq.com/llms/processes/model.md)
- [Node types](https://docs.vertesiahq.com/llms/processes/node-types.md)
- [Observability](https://docs.vertesiahq.com/llms/processes/observability.md)
- [Processes](https://docs.vertesiahq.com/llms/processes/overview.md)
- [Task Inbox](https://docs.vertesiahq.com/llms/processes/task-inbox.md)
- [Tutorial: Contract Review](https://docs.vertesiahq.com/llms/processes/tutorial-contract-review.md)
- [Getting Started](https://docs.vertesiahq.com/llms/quickstart.md)
- [Configuration](https://docs.vertesiahq.com/llms/semantic/configuration.md)
- [Getting started](https://docs.vertesiahq.com/llms/semantic/getting-started.md)
- [Overview](https://docs.vertesiahq.com/llms/semantic/overview.md)
- [Studio Assistant](https://docs.vertesiahq.com/llms/studio/assistant.md)
- [Interactions](https://docs.vertesiahq.com/llms/studio/interactions.md)
- [Prompts](https://docs.vertesiahq.com/llms/studio/prompts.md)
- [Release Notes Generation](https://docs.vertesiahq.com/llms/use-cases/release-notes-generation.md)
- [Workflow Activities](https://docs.vertesiahq.com/llms/workflows/activities-catalog.md)
- [Configuration](https://docs.vertesiahq.com/llms/workflows/configuration.md)
- [Workflows for Agentic AI](https://docs.vertesiahq.com/llms/workflows/overview.md)
- [Workflow DSL](https://docs.vertesiahq.com/llms/workflows/workflow-dsl.md)

---

## Vertesia Documentation: Engineering Reliable Generative AI for the Enterprise

Source: https://docs.vertesiahq.com
Markdown: https://docs.vertesiahq.com/llms/index.md

A Unified, API-First Platform for Architects and Developers to Build, Deploy, and Scale Specialized AI Agents and Applications with Unparalleled Accuracy and Operational Resilience.


## Addressing Critical Challenges in Enterprise GenAI

Enterprise generative AI initiatives often stall in production due to inherent technical complexities.:

**LLM Hallucinations & Data Fidelity:** Achieving enterprise-grade accuracy (beyond 95%) is critical. Generic LLMs struggle with factual consistency, leading to unreliable outputs that undermine trust in mission-critical applications.

**Complex & Time-Consuming Data Preparation**: Up to 50% of GenAI development time is consumed by preparing unstructured enterprise data for Retrieval-Augmented Generation (RAG) pipelines, delaying time-to-market.

**Operationalizing at Scale:** Moving from Proof-of-Concept (PoC) to production-ready GenAI solutions is a significant hurdle due to integration complexities, scalability demands, and lack of robust deployment frameworks.

**Architectural Resilience & Security:** Designing AI systems that are secure, compliant (e.g., SOC2 Type II), and resilient against model failures or provider lock-in requires sophisticated architectural patterns and robust governance.

**Vendor Lock-in & Model Agnosticism:** Enterprises need flexibility to integrate with diverse LLMs and cloud infrastructures without being tied to a single provider."

## Vertesia: A Robust Platform for Enterprise GenAI

Vertesia provides the foundational capabilities and architectural patterns necessary for building reliable, scalable, and secure generative AI applications and agents.

  ![Architecture](https://vertesiahq.com/hs-fs/hubfs/Vertesia-Diagram-RGB-min.jpg?width=3000&height=1700&name=Vertesia-Diagram-RGB-min.jpg)

**Semantic DocPrep™:** Precision RAG via Structured XML: Our agentic API service intelligently transforms complex, unstructured enterprise documents (e.g., reports, regulatory filings) into richly structured, semantically tagged XML. This ensures LLMs receive high-fidelity, contextualized data, dramatically improving RAG accuracy and and reducing hallucinations.

**Virtualized LLMs:** Resilient & Optimized Inference: Connect to and orchestrate workloads across multiple LLM providers and models (AWS, GCP, Azure, OpenAI, etc.). Our virtualized LLM layer provides dynamic failover for continuous uptime, intelligent load balancing for cost/performance optimization, and continuous fine-tuning capabilities.

**Durable AI Orchestration:**: Design and orchestrate long-running AI agents and workflows, from minutes to days. Our platform preserves the state of generative processes and agentic decisions, ensuring reliable resumption after system failures or network interruptions. This capability is critical for maintaining consistency, preventing data loss, and enabling robust, extended AI tasks in production environments.

**Enterprise-Grade Security & Governance by Design:** Built with SOC2 Type II compliance, flexible data residency options, bias mitigation tools, and end-to-end auditability. Deploy on-premises, in your private cloud, or via our multi-cloud SaaS for stringent security and regulatory adherence.

**Simplified and Accelerated Development:** We abstract away the complexities of infrastructure management, letting you zero in on what matters: designing and executing production-ready Agentic AI workflows. With Vertesia, you'll use straightforward code and configuration to build sophisticated AI pipelines, dramatically accelerating your development cycle. Focus on the logic and intelligence of your agents, not on provisioning services or managing dependencies.

---

## Configuration

Source: https://docs.vertesiahq.com/agent-runner/configuration
Markdown: https://docs.vertesiahq.com/llms/agent-runner/configuration.md

In the previous section, we explored the execution of the `Multipurpose Agent`. This section will guide you through the process of configuring a new AI Agent within Vertesia Studio.

## Agents

Utilizing interactions offers a straightforward and efficient method for creating specialized agents. We will revisit the example from the "Getting Started" section to configure an agent whose sole purpose is to generate lease agreements.

### Prompt Creation

Before proceeding with interaction configuration, it is essential to create a prompt.

1. Navigate to the Prompts section in Vertesia Studio.
2. Create a new prompt and name it `Agent Lease Agreement`.
3. Configure the prompt parameters by copying and pasting the following JSON structure into the JSON editor:

    ```json
    {
      "type": "object",
      "properties": {
        "agreements": {
          "type": "array",
          "items": {
            "type": "string"
          },
          "editor": "textarea",
          "format": "textarea"
        },
        "collectionName": {
          "type": "string"
        },
        "documentType": {
          "type": "string"
        }
      },
      "required": [
        "agreements",
        "collectionName",
        "documentType"
      ]
    }
    ```

4. Copy the following prompt content:

    ```js
    return `
    Generate ${agreements.length} lease agreement documents for commercial office space, each tailored to different specifications:

    ${agreements.map((agreement,index) => `${index+1}. ${agreement}`).join('\n')}

    A lease agreement document must have the following metadata properties:
    - space size in square feet
    - term length
    - monthly cost
    - deposit amount
    - state
    - city
    - address
    - property manager

    If a document type named  ${documentType} doesn’t already exit, create it with properties listed above

    Subsequently, these documents should be organized and stored within a collection named ${collectionName}". Create a collection only if doesn't already exists
    `
    ```

5. Finally, to validate the prompt configuration, copy the provided test data:

    ```json
    {
      "agreements": [
        "A basic, cost-effective option suitable for approximately eight workstations.",
        "A mid-range option accommodating approximately twenty workstations, three meeting rooms, and two individual phone booths.",
        "A premium option located on the fortieth floor or above in an office building, designed to accommodate approximately fifty workstations, ten meeting rooms, and ten individual phone booths."
      ],
      "collectionName": "Commercial Lease Agreements",
      "documentType": "CommercialLeaseAgreement"
    }
    ```

### Interaction Configuration

With the prompt successfully configured, we can now proceed to set up the interaction.

1. Go to the Interactions section in Vertesia Studio.
2. Create a new interaction and assign it the name `Lease Agreement Agent`.
3. In the "Prompts" tab, you will add two distinct prompts. First, incorporate the pre-configured `Agent Basis` system prompt, which furnishes the agent with general operational guidelines. Subsequently, add the `Agent Lease Agreement` prompt that you configured earlier.

   ![Interaction Agent Prompts](/agents/interaction_prompts.png)

4. Navigate to the **Agent Runner** tab to configure the agent-specific settings:

   ![Agent Runner Configuration Tab](/agents/interaction_agent_runner_tab.png)

   In this tab, you can configure the following settings:

   - **Executable as Agent**: Check this option to make the interaction available in the Agent Runner selection dialog. When enabled, this interaction will appear in the list of available agents.
   - **Available as Sub-agent (Tool)**: Check this option to make the interaction available as a tool that other agents can call during execution.
   - **Default Tools**: Specify a list of default tools that this agent should have access to during execution. These tools can be builtin tools, custom tools, remote mcp server tools or other interactions tagged or configured as tools. Note that this setting can be overridden at execution time.
   - **Default Content Scope (Collection)**: Optionally select a collection to limit the agent's access to content within that specific collection. When set, the agent can only retrieve and search documents from the specified collection, providing better control over the data the agent can access. This setting can also be changed at execution time.

   ***Enabling "Executable as Agent" is crucial for the interaction to become discoverable and available within the Agent Runner.***

### Agent Runner Execution

Finally, return to the Agent Runner via the left-hand navigation menu. You can now select the `Agent Lease Agreement` interaction, populate the necessary parameters, and execute your first specialized agent.

![Custom Agent Run](/agents/custom_agent_run.png)

## Further Exploration

This section has demonstrated how to configure and execute a specialized agent within Vertesia Studio. In the upcoming section, we will delve into initiating an agent using the Vertesia TypeScript SDK.

---

## Custom Tools

Source: https://docs.vertesiahq.com/agent-runner/custom-tools
Markdown: https://docs.vertesiahq.com/llms/agent-runner/custom-tools.md

Vertesia supports **custom tools** to let you integrate your own logic or connect to external data sources beyond the built-in tools it provides. These tools can be written in any programming language and exposed via a RESTful HTTP interface. Once registered, Vertesia acts as a **broker between the LLM and your custom tools**.

When the LLM issues a `tool_use` message, Vertesia identifies whether the requested tool is built-in or custom. If it's a custom tool, Vertesia sends a `POST` request to your tool server, waits for the response, and then passes the result back to the LLM.

Your tool server must expose an endpoint that handles both `GET` and `POST` HTTP methods:

- `GET` is used to **discover the tools** exposed by your server.
- `POST` is used to **invoke a tool** with user-provided input when requested by the LLM.

## Authentication

The `POST` request includes a **Vertesia-signed JWT** that contains metadata such as the user ID, roles, project, and organization. Your tool server must validate this token using Vertesia's public key, which is available via a JWKS endpoint:
`{vertesia_server}/api/v1/.well-known/jwks`

You can find the `{vertesia_server}` location in the token property `endpoints.studio`.

To find the correct signing key, use the `kid` (key ID) field from the JWT header to match it with a key in the JWKS.

---

## API Specification

### `GET /path/to/tools/endpoint`

This endpoint returns the list of tools available on the server. No authorization is required for this request.

The response must be a JSON object describing the tool server and its available tools.

Here's the TypeScript interface for the expected response:

```ts
interface GetToolsResponse {
  /**
   * The URL of the tool server (same as the URL where this response is served)
   */
  src: string;

  /**
   * A human-readable title for this tool server
   */
  title: string;

  /**
   * A short description of the tool server
   */
  description: string;

  /**
   * The list of tools exposed by this server
   */
  tools: {
    /**
     * The name of the tool (used in tool_use messages)
     */
    name: string;

    /**
     * A short description of what the tool does
     */
    description: string;

    /**
     * A JSON Schema describing the expected input for the tool
     */
    input_schema: JSONSchema;
  }[];
}
```

Where JSONSchema is a standard JSON Schema definition of the tool's input.

**Example Response:**

```json
{
  "src": "http://localhost:5173/api/test",
  "title": "Development Tools",
  "description": "A collection of test tools for development purposes",
  "tools": [
    {
      "name": "weather",
      "description": "Get the current weather for a given location.",
      "input_schema": {
        "type": "object",
        "properties": {
          "location": {
            "type": "string",
            "description": "The location to get the weather for, e.g., 'New York, NY'."
          }
        },
        "required": [
          "location"
        ]
      }
    }
  ]
}
```

### `POST /path/to/tools/endpoint`

This endpoint is called by Vertesia when the LLM requests the execution of a specific tool. The request includes the tool name, input arguments, and a signed JWT in the `Authorization` header. Your server must verify the JWT before executing the tool logic.

### Request Headers

```http
Authorization: Bearer <vertesia_jwt_token>
Content-Type: application/json
```

### Request Body

The request body contains information about the tool being used and optional metadata about the execution context.

```ts
interface ToolExecutionRequest {
    /**
     * Contains the name of the tool to execute and the input arguments.
     */
    tool_use: ToolUse;

    /**
     * Optional metadata related to the current execution context.
     */
    metadata?: Record<string, any>;
}

interface ToolUse {
    /**
     * The unique ID of this tool use request (used for traceability).
     */
    id: string;

    /**
     * The name of the tool to execute (must match the name provided in the GET response).
     */
    tool_name: string;

    /**
     * The arguments to pass to the tool (must match the tool's input_schema).
     */
    tool_input: unknown;
}
```

### Successful Response

On success, your tool server must return a JSON response that includes the tool use ID and the result of the tool execution.

```ts
interface ToolExecutionResponse {
    /**
     * The tool use id of the tool use request. For traceability.
     */
    tool_use_id: string;

    /**
     * The tool result as a string (can be a serialized JSON object).
     */
    content: string;

    /**
     * Optional file URLs to attach to the response. Useful for sending images to the LLM.
     */
    files?: string[];

    /**
     * Metadata can be used to return more info on the tool execution like stats or user messages.
     */
    metadata?: Record<string,any>
}
```

### Error Response

If an error occurs during execution, your server must return a non-2xx HTTP status code and a JSON body describing the error.

```ts
interface ToolExecutionResponseError {
    /**
     * The tool use ID of the request (for traceability).
     */
    tool_use_id: string;

    /**
     * The HTTP status code.
     */
    status: number;

    /**
     * A short error message.
     */
    error: string;

    /**
     * Optional additional details about the error.
     */
    data?: Record<string, any>;
}
```

### Response Headers

Your tool server should include standard HTTP headers in all responses. the `Content-Type` should be set on `application/json`.

---

## Streaming Large Tool Outputs to Artifacts

Some tool executions can produce large textual outputs (for example, long reports, detailed logs, or large JSON payloads). To keep responses manageable for the model while still preserving full results, tools can accept an optional `output_artifact` parameter in their input schema.

When present, Vertesia will:

- Store the full tool output as an artifact in the agent workspace (for example, under a `files/` path).
- Return a compact response that includes a short preview and the `artifact_path` pointing to the full content.

The agent can then:

- Use the artifact tools (`read_artifact`, `list_artifacts`, `grep_artifacts`, `patch_artifact`) to inspect or refine the result.
- Use `execute_shell` to run follow-up processing over the artifact inside the Daytona sandbox.

This pattern is particularly useful for remote tools and skills that generate large analysis reports or intermediate datasets that would otherwise exceed the model context.

---

## Building a Custom Tool Server

### Quick Start with TypeScript

The fastest way to build a custom tool server is using the `@vertesia/create-plugin` scaffold:

```bash
npm init @vertesia/plugin@latest
```

The generated project is a unified plugin with both a Hono tool server and a React UI, including example tools and skills. The comprehensive README covers creating resources, local development, building, and deployment.

### Manual Setup

If you prefer to build from scratch, use the [`@vertesia/tools-sdk`](https://www.npmjs.com/package/@vertesia/tools-sdk) package directly. The SDK implements the entire protocol for you, including:

- Handling GET and POST endpoints
- JWT verification and validation against Vertesia's JWKS
- Input schema validation
- Tool routing and execution handling

Using the SDK helps you focus on writing tool logic instead of boilerplate.

## Deploying to Vercel

[Vercel](https://vercel.com) is the easiest way to deploy your tool server — its generous free tier is more than enough for development and small-scale production. The generated project includes a `vercel.json` and `api/index.js` serverless adapter. Deploy with:

```bash
npm i -g vercel
vercel --prod
```

Static files (UI builds) are served from `dist/`, and API requests are routed through the serverless function. After deploying, note the production URL (e.g. `https://my-tools.vercel.app`).

**Important**: Disable deployment protection in Vercel project settings so Vertesia can reach your tool server endpoint.

## Registering Custom Tools

To make your custom tools available to Vertesia Agent Runner, register your tool server as a Vertesia **Application**. Create a `manifest.json` with your deployed endpoint:

```json
{
  "name": "my-tools",
  "title": "My Tools",
  "publisher": "your-org",
  "visibility": "private",
  "status": "beta",
  "endpoint": "https://my-tools.vercel.app/api"
}
```

Then use the CLI to create and install it in your project:

```bash
vertesia apps create --install -f manifest.json
```

The `endpoint` URL points to your tool server. Vertesia will call `GET` on it to discover available tools and `POST` to execute them. Once registered, your custom tools are available to agents just like built-in ones.

For more details on applications, refer to the [Applications](/apps/overview) section.

---

## Email Integration

Source: https://docs.vertesiahq.com/agent-runner/email-integration
Markdown: https://docs.vertesiahq.com/llms/agent-runner/email-integration.md

Vertesia agents support bidirectional email communication, enabling users to interact with agents through their email client. This guide covers the three main email interaction patterns.

## Overview

Email integration enables three distinct interaction patterns:

| Pattern | Trigger | Description |
|---------|---------|-------------|
| **Email-started workflow** | User sends email to agent | User initiates a new agent session by sending an email to a project address |
| **Agent response email** | Agent completes or waits for input | Agent sends results or questions via email; user can reply to continue |
| **Ask user via email** | Agent uses `ask_user` tool | Agent asks questions via email when the user channel is set to email |

All three patterns use a reply-to address format that routes responses back to the correct workflow run.

## Prerequisites

Email integration is configured in **Project Settings > Integrations > Resend Email**.

### Required Settings

| Setting | Description |
|---------|-------------|
| `API Key` | Your [Resend](https://resend.com) API key for sending emails |
| `Inbound Domain` | Domain for sending/receiving email (e.g., `mail.yourcompany.com`) |
| `Webhook Secret` | Resend webhook signing secret for verifying inbound emails |

### Security Settings (Optional)

| Setting | Default | Description |
|---------|---------|-------------|
| `Require Project Access` | Enabled | Only users with project access can start agents via email |
| `Require Email Authentication` | Enabled | Inbound emails must pass DKIM/SPF authentication |
| `Allowed Sender Domains` | Empty (all) | Whitelist of allowed sender email domains |

### DNS Configuration

Your inbound domain requires:

1. **MX records** pointing to Resend's inbound servers
2. **SPF and DKIM records** for email authentication
3. **Webhook configuration** in Resend pointing to `https://your-studio-url/webhooks/resend`

## Pattern 1: Email-Started Workflows

Users can start new agent sessions by sending an email to a project-specific address. This enables email-first workflows where users never need to access another UI.

### Email Address Format

```
{namespace}+{interaction-name}@{inbound_domain}
```

For example:
- `acme+review-contract@mail.vertesia.io` - Start the `review-contract` agent in the `acme` project
- `myproject+analyst@mail.vertesia.io` - Start the `analyst` agent in the `myproject` project

### Access Control

When a user sends an email to start a workflow, the following checks are performed:

1. **Email authentication** (if enabled): DKIM and SPF are verified
2. **Sender domain whitelist**: If `Allowed Sender Domains` is configured, the sender must match
3. **Project access** (if enabled): The sender's email must belong to a user with access to the project
4. **Interaction access**: The interaction must exist and be configured as an agent

### Interaction Data Fields

When a workflow starts via email, the interaction receives the following data fields that can be used in the prompt template:

| Field | Type | Description |
|-------|------|-------------|
| `${message}` | string | The email body (plain text preferred, falls back to HTML) |
| `${email.from}` | string | Sender email address |
| `${email.subject}` | string | Email subject line |
| `${email.source}` | string | Always `"email"` |
| `${email.message_id}` | string | Unique email message ID |
| `${email.has_attachments}` | boolean | Whether the email has attachments |
| `${attachments}` | array | Uploaded attachment metadata (populated async) |

**Example interaction prompt:**

```
# Email Task

## Persona
You are an assistant that processes requests received via email.

## Email Content
**Email from:** ${email.from}
**Subject:** ${email.subject}

**Request:**
${message}

## Task
- Please analyze this request and respond appropriately.
```

### How It Works

1. User sends email to `{namespace}+{agent}@{domain}`
2. Resend webhook receives the email and sends it to Vertesia
3. System verifies sender access and email authentication (based on configuration)
4. A new workflow starts with:
   - Initial message from email body
   - Email channel automatically configured in `user_channels` (with sender's email as `to_email` and thread info)
   - Email metadata (subject, from, attachments) available to the agent
5. Agent processes the request and can use `ask_user` to ask follow-up questions via email
6. User receives responses in their email client and can reply to continue
7. **When the workflow completes, the final result is automatically sent to the user via email**

### Attachments

Email attachments are automatically:
1. Downloaded from Resend
2. Uploaded to the workflow's artifact storage
3. Made available to the agent for processing

## Pattern 2: Agent Response Emails

When a workflow has an email channel configured in `user_channels`, the agent automatically sends emails to the user:

- **Completion emails**: When a non-interactive workflow finishes
- **Response emails**: When an interactive workflow produces output and waits for input
- **Ask user emails**: When the agent needs to ask questions

### How It Works

1. Workflow runs with email channel in `user_channels` (automatic for email-started workflows)
2. Agent processes the request and produces output
3. System sends email with results and a reply-to address: `r+{routeKey}@{inbound_domain}`
4. When user replies, the email is received by the inbound domain
5. Resend sends a webhook to Vertesia
6. The webhook extracts the run ID and sends a `UserInput` signal to the workflow
7. The workflow continues with the user's reply as input

### Email Threading

All emails in a conversation maintain proper threading:
- Subject line preserves `Re: Original Subject`
- `In-Reply-To` and `References` headers enable proper threading in email clients
- Users see all messages in a single thread

## Pattern 3: Ask User via Email

When the workflow has an email channel configured in `user_channels`, the `ask_user` tool sends questions via email in addition to displaying them in the UI.

### Configuration

Configure `user_channels` when starting the agent run, either via the [Agent Runs API](/api/agents) or as workflow variables:

```typescript
const run = await client.store.agents.start({
    interaction: "my-agent",
    interactive: true,
    user_channels: [
        {
            type: "email",
            to_email: "user@example.com"
        }
    ]
});
```

Or equivalently as workflow variables (for scheduled or rule-triggered workflows):

```json
{
    "vars": {
        "interaction": "my-agent",
        "user_channels": [
            {
                "type": "email",
                "to_email": "user@example.com"
            }
        ]
    }
}
```

The email channel supports email threading with optional fields:
- `thread_subject`: Subject for the email thread (without "Re:" prefix)
- `in_reply_to`: Message ID for the In-Reply-To header
- `references`: Array of message IDs for the References header

### How It Works

1. Agent calls `ask_user` with questions
2. System detects email channel in `user_channels` and sends an email to the configured recipient
3. The email contains the questions with context from the conversation
4. User replies to the email with their answers
5. Reply is routed back to the workflow and the agent continues

### Email Formulation

When sending ask_user questions via email, the system uses an LLM to formulate a well-contextualized email that includes:

- Recent conversation context
- The current task being worked on
- The questions being asked
- Instructions for replying

## Automatic Completion Email

When an email channel is configured in `user_channels` (automatically set for email-started workflows), the system will automatically send the workflow result to the user via email when the workflow completes. This ensures users who interact via email always receive their results without needing UI access.

The completion email:
- Includes the full workflow output
- Maintains email threading if part of an existing conversation
- Uses the same reply-to address format, allowing users to continue the conversation

## Reply Address Format

All email flows use a consistent reply-to address format:

```
r+{routeKey}@{inbound_domain}
```

Where `runId` is the Temporal workflow run ID with dashes removed (to comply with RFC 5321's 64-character local-part limit).

When a reply is received:
1. The run ID is extracted from the address
2. Temporal is queried to find the workflow
3. Project and account IDs are retrieved from search attributes
4. The Resend integration settings are used for webhook verification
5. The email content is sent as a `UserInput` signal to the workflow

## Best Practices

### For Email-Started Workflows

- Modify the example prompt to specialize agents
- Handle email attachments appropriately in your agent logic
- Consider user/agent access to content and tools
- Consider all security recommendations

### Security Recommendations

- **Keep `Require Email Authentication` enabled** to prevent email spoofing
- **Keep `Require Project Access` enabled** unless you specifically need public access
- Use `Allowed Sender Domains` to restrict access to specific organizations
- Regularly rotate your Resend API key and webhook secret

## Troubleshooting

### Emails Not Being Received

1. Verify MX records are correctly configured
2. Check Resend webhook is active and pointing to the correct URL
3. Verify `Webhook Secret` matches between Resend and your Vertesia configuration

### Replies Not Reaching Workflow

1. Check the workflow is still running (not completed or timed out)
2. Verify the reply-to address format is correct
3. Check Resend webhook logs for delivery status

### Email-Started Workflows Failing

1. If `Require Project Access` is enabled, verify the sender has project access in Vertesia
2. If `Require Email Authentication` is enabled, check DKIM/SPF authentication is passing
3. Verify the interaction exists and is configured as an agent
4. Check the Resend integration is enabled in Project Settings > Integrations

---

## Execution Model

Source: https://docs.vertesiahq.com/agent-runner/execution-model
Markdown: https://docs.vertesiahq.com/llms/agent-runner/execution-model.md

An Agent Runner agent is not a single model call. It is a loop: the agent works toward a goal, decides what to do next, acts, observes the result, and decides again — until the task is done.

## The loop

Each turn of an agent run follows the same cycle:

1. **Decide.** The model is given the goal, the conversation so far, and the set of tools available to it. It chooses the next action.
2. **Act.** When the model requests a tool, the executor runs it. The tool may be [built-in](/agent-runner/tools), a [custom tool](/agent-runner/custom-tools) served over HTTP, or an [MCP tool](/agent-runner/mcp-tools).
3. **Observe.** The tool's result is returned to the model.
4. **Repeat.** With the result in hand, the model decides the next action — call another tool, refine its plan, ask the user, or finish.

The control flow is not fixed in advance. It emerges from the model reasoning over what it finds at each step. This is the difference between an agent and a scripted workflow: the sequence of actions is decided at run time, not drawn beforehand.

> For deterministic, auditable business processes where the path *should* be fixed, use the [Process Engine](/processes/overview) instead — there, the engine owns control flow and an agent runs as a bounded worker inside a node. The two models are complementary, and the [Process Engine documentation](/processes/model) explains when to reach for each.

## Planning and decomposition

Agents can reason about their own approach. Built-in `think`, `plan`, and `update_plan` tools let an agent lay out and revise a plan as it learns more, rather than committing to a single sequence up front. For large tasks, an agent can spin up parallel sub-agents — see [Workstreams](/agent-runner/workstreams) — and coordinate their results.

## How a run ends

An agent run is bounded. A run completes when:

- the model has no further actions to take and returns its final result, or
- a tool signals completion (for example, the agent ends the conversation explicitly).

Interactive agents, rather than ending, wait for the next user message and resume on input.

To keep autonomous runs safe, the runtime applies guardrails independent of the model's own judgment: a maximum number of tool iterations before prompting the model to reassess, and automatic loop-detection that stops an agent repeating the same action without progress. These reduce the risk of runaway loops, even if a task is mis-specified.

## Durability

The agent loop runs on Vertesia's durable workflow engine. State — the conversation, the plan, intermediate results — is preserved as the run proceeds, so a long-running agent survives worker restarts and resumes where it left off rather than starting over. This is what makes runs that span minutes, hours, or days reliable; see [Workflows](/workflows/overview) for the underlying execution guarantees.

---

## Agent Runner - Getting Started

Source: https://docs.vertesiahq.com/agent-runner/getting-started
Markdown: https://docs.vertesiahq.com/llms/agent-runner/getting-started.md

This guide provides a comprehensive, step-by-step walkthrough for executing your initial AI Agent within Vertesia Studio. Vertesia Studio offers an intuitive and efficient environment for experimenting with AI Agents. Users can easily select an interaction, define its parameters, and initiate agent execution.

To maintain simplicity, this documentation utilizes basic examples that do not necessitate external configurations or third-party dependencies.

## Prerequisites

Agents in Vertesia are designed to accept an **Interaction** as input. If you are unfamiliar with the concept of interactions, please refer to the dedicated [documentation](/studio/interactions).

## Generic Agent

Upon project creation, Vertesia Studio provides a sample configuration that includes a pre-configured generic agent interaction,
dubbed `Multipurpose Agent` This interaction can be located in Interactions.

## Run an Agent

To initiate an agent run, navigate to Agent Runner, and select the `Multipurpose Agent` interaction.

The left panel of the Agent Runner interface presents several configurable parameters for agent execution:

| Parameter      | Description                                 | Default Value      |
|----------------|---------------------------------------------|--------------------|
| Interactive    | When set to `true`, the agent will dynamically respond to messages during its execution and remain in a standby state for a period of 7 days.  Conversely, if set to `false`, the agent will only respond to messages when a tool explicitly requests user input and will terminate upon task completion. | True |
| Environment    | Specifies the Vertesia environment utilized for Large Language Model (LLM) inference. | Interaction default environment |
| Model          | Defines the AI model to be employed by the agent. Only models that support tool integration are selectable. | Interaction default model |
| Tools          | Comma separated list of tools made available to the agent during its execution.  Prefix operators can be used to add or remove some tools: **+** prefix to add a tool **-** prefix to remove a tool  Examples:  `-web_search` means all the built-in tools except `web_search`  `-web_search,+my_custom_tool` means all the built-in tools plus `my_custom_tool` and without `web_search`  | empty (all [built-in tools](./tools)) |

The `Environment` and `Model` parameters should be prefilled with the `Multipurpose Agent` default configuration.

Below these agent-specific parameters, a form for interaction parameters is displayed. For the `Multipurpose Agent`, this form contains a single field labeled `task`.

![Agent Runner Input Form](/agents/agent_runner_input_form.png)

Let's proceed by submitting the following task to the agent:

```text
Generate three lease agreement documents for commercial office space, each tailored to different specifications:

- A basic, cost-effective option suitable for approximately eight workstations.
- A mid-range option accommodating approximately twenty workstations, three meeting rooms, and two individual phone booths.
- A premium option located on the fortieth floor or above in an office building, designed to accommodate approximately fifty workstations, ten meeting rooms, and ten individual phone booths.

A lease agreement document must have the following metadata properties:
- space size in square feet
- term length
- monthly cost
- deposit amount
- state
- city
- address
- property manager

If a document type named  “Lease Agreement” doesn’t already exit, create it with properties listed above

Subsequently, these documents should be organized and stored within a collection named "Office Leases."
```

This task extends beyond simple content generation via LLM inference. It requires the agent to perform a sequence of operations without a pre-configured structured workflow. Specifically, the agent will:

- generate random content using the metadata structure provided in the prompt
- create a document type in vertesia
- create 3 documents
- create a collection
- group the documents in the collection

Once the task is entered, click **Start** to initiate agent execution.

The agent's progress will be dynamically displayed in the chat interface on the right side of the screen.

![Agent Conversation](/agents/agent_conversation.png)

By default, only the most critical messages are shown in the conversation log. To view the complete conversation history, click on `Details`. Furthermore, the agent is configured to generate and update a plan, reflecting its progress on the assigned task.

During execution, you can click on the provided links within the messages to access the documents created by the agent. To return to the agent conversation, simply use your browser's back button.

## Send Messages

If the agent run was initiated in interactive mode, an input box will be visible at the bottom of the chat history. This allows you to send real-time messages to the agent, potentially altering its ongoing execution.

If the run was not started in interactive mode, the input box will only appear when the agent explicitly awaits user input.

For instance, while the agent is completing its initial task or after its completion, you can send the following message:

```text
Create a spreadsheet with the agreement properties and add it to the collection
```

![Send Message](/agents/agent_send_messages.png)

Behind the scenes, the agent now uses skills plus the `execute_shell` and artifact tools to implement spreadsheet creation and analysis, rather than relying on dedicated spreadsheet-specific built-in tools.

## Going Further

Having successfully executed a generic agent in Vertesia Studio, we encourage you to experiment with diverse tasks and tools relevant to your specific use cases. A comprehensive list of all built-in tools is available [here](./tools).

As an example, consider utilizing the [web_search](./tools#web-and-external-tools) tool to enable the agent to retrieve information from the internet as part of a task. (Note: This activity leverages [Serper](https://serper.dev/) and requires the configuration of an API key.)

The subsequent section of this documentation will detail the process of configuring a new agent from scratch.

---

## Identity & Access

Source: https://docs.vertesiahq.com/agent-runner/identity
Markdown: https://docs.vertesiahq.com/llms/agent-runner/identity.md

Every agent run executes under its own identity. Vertesia does not hand an agent a user's session token or run agents under a shared service account. Instead, each run gets a dedicated, scoped, short-lived credential, and every action the agent takes is authorized and audited against it.

## A per-run agent identity

When an agent starts, Vertesia's security token service mints a dedicated token for that run:

- **Its own principal.** The token identifies an `agent` principal with a unique, per-run subject — not the launching user, and not a shared account.
- **Scoped to one project.** The token is bound to a single account and project, verified at mint time. An agent cannot reach across projects it was not launched in.
- **Short-lived.** The token is time-bound. Long-running agents (see [Workstreams](/agent-runner/workstreams) and [Workflows](/workflows/overview)) re-mint fresh credentials as they run rather than holding a single long-lived token.

## Acting on behalf of the launching user

An agent acts **on behalf of** the user (or workload) that launched it. The per-run token carries that user's verified identity — their roles, group memberships, application access, and content-security rules — and every permission check and every content query resolves against it.

Two consequences follow, and both matter for enterprise use:

- **An agent can do what its launcher can do — and never more.** Authority is bounded by the launching identity. An agent launched by a user with read-only access to a collection cannot write to it, regardless of what the model decides to attempt.
- **The launching identity is verified, not asserted.** The on-behalf-of identity is checked (signature and account) when the agent token is minted, so an agent cannot escalate by claiming an authority it was not given.

## Authorization is enforced everywhere the agent acts

The same identity is enforced consistently across the platform — not only at the API boundary:

- **Tool calls** are authorized against the agent's effective identity before they run.
- **Content access** is filtered by the launching user's permissions and content-security (attribute-based) rules. An agent's search and retrieval return only what that identity is allowed to see.
- **Application and tool-surface access** is gated by the apps the launching identity is entitled to.

## Auditing

Actions are attributed to both the agent principal and the identity it acted for. The audit trail records *which agent* performed an action and *on whose authority* it ran, so an agent's activity is never anonymous and never detached from an accountable human or workload.

## Scoping an agent

Beyond the inherited user permissions, an agent run can be narrowed further:

- **Project and account** — bound at mint time.
- **Collection** — an agent can be restricted to a specific content collection, limiting both what it can read and where it can write.
- **Application access** — the tool surfaces and integrations an agent can reach follow the launching identity's app entitlements.
- **Content security** — read, write, and delete are governed by the same attribute-based rules that apply to users, applied as a query filter at access time.

## Scheduled and headless runs

Agents launched on a schedule run on behalf of the identity that created the schedule, captured when the schedule is defined. Agents invoked through the SDK or API run on behalf of the calling identity. In both cases the same rule holds: the agent is bounded by, and audited against, the identity it runs for.

---

## MCP Tools

Source: https://docs.vertesiahq.com/agent-runner/mcp-tools
Markdown: https://docs.vertesiahq.com/llms/agent-runner/mcp-tools.md

Vertesia agents can use tools provided by external [MCP (Model Context Protocol)](https://modelcontextprotocol.io) servers. MCP servers are configured in [application manifests](/apps/tool-collections) and their tools become available to agents alongside built-in and custom tools.

## How It Works

1. An application manifest declares one or more MCP servers in its `tool_collections` array.
2. When the application is installed on a project, the MCP tools are discovered and made available to agents.
3. Tool names are prefixed with the configured `namespace` to avoid collisions (e.g., a tool named `search` with namespace `crm` becomes `crm_search`).
4. If an MCP server requires OAuth, Vertesia manages that connection through the **remote MCP connections** flow in Studio Server. This is the control-plane API used to discover OAuth metadata, start authorization, exchange codes, check status, and disconnect remote MCP accounts.

## OAuth Authentication

Many MCP servers require OAuth 2.0 authentication. Each user must connect their account before an agent can use the MCP tools on their behalf.

### Connecting

Users connect from the **new agent form** in the Agent Runner interface. The form displays connect actions for any MCP tools that require authentication. Users must connect before starting the agent.

Behind the scenes, Vertesia treats these as **remote MCP connections**:

- Vertesia looks up the installed app and target MCP collection.
- If the collection is bound to a project-level OAuth Provider, Vertesia uses that provider configuration.
- Otherwise, Vertesia can fall back to OAuth discovery on the remote MCP server itself.
- Tokens are stored securely and refreshed automatically.

If a refresh token expires or is revoked, the user will need to reconnect.

### Connection Modes

Remote MCP collections can authenticate in two main ways:

- **OAuth Provider-backed**: the MCP collection is linked to a project-level [OAuth Provider](/api/oauth-providers). This is the preferred path when you want explicit control over client ID, secret, scopes, and lifecycle.
- **Remote server discovery**: if no provider is configured, Vertesia can discover OAuth metadata from the remote MCP server and follow that server's authorization flow.

For `client_credentials` providers, Vertesia can connect server-side without requiring an interactive browser flow.

## Configuration

MCP servers are configured in application manifests, not in the Agent Runner directly. See [MCP Servers](/apps/tool-collections) for the full configuration reference including:

- Required and optional fields
- OAuth setup with project-level OAuth Providers, embedded `oauth_config`, and remote server discovery
- Namespace configuration
- Complete manifest examples

---

## Agent Message System

Source: https://docs.vertesiahq.com/agent-runner/messaging
Markdown: https://docs.vertesiahq.com/llms/agent-runner/messaging.md

The agent message system is Vertesia's real-time communication layer between agent workflows and client applications. It handles message creation, delivery, streaming, and rendering through a well-defined pipeline.

**Use the `ModernAgentConversation` component** for building agent UIs. It handles streaming, reconnection, plan visualization, file uploads, and message rendering out of the box. See [Embedding the Conversation UI](#embedding-the-conversation-ui) below.

## Architecture Overview

Messages flow through the following pipeline:

```
User Input --> Signal (UserInput) --> Temporal Workflow
                                          |
                                  Agent/Tool Execution
                                          |
                              postUpdateMessage activity
                                          |
                          POST /runs/:runId/updates
                                          |
                             Redis (list + pub/sub)
                                          |
                  +--------------------------------------+
                  | Phase 1: GET /updates (gzip)         |
                  | Phase 2: SSE /stream (real-time)     |
                  +--------------------------------------+
                                          |
                       ModernAgentConversation UI
```

**Key participants:**

- **Temporal Workflows** orchestrate agent execution and tool calls
- **Redis** stores message history (up to 1000 messages, 90-day TTL) and delivers real-time updates via pub/sub
- **zeno-server** exposes REST and SSE endpoints for message retrieval and streaming
- **`@vertesia/client`** provides the `WorkflowsApi` that handles both historical fetch and real-time SSE
- **`@vertesia/ui`** provides `ModernAgentConversation`, the recommended UI component

## Message Types

Every agent message has a `type` field from the `AgentMessageType` enum (`@vertesia/common`):

| Type | Value | Description |
|------|-------|-------------|
| `SYSTEM` | 0 | Internal system messages (e.g., file processing status) |
| `THOUGHT` | 1 | Agent reasoning and intermediate thinking |
| `PLAN` | 2 | Plan proposals with structured tasks |
| `UPDATE` | 3 | Progress updates during execution |
| `COMPLETE` | 4 | Task or workstream completion |
| `WARNING` | 5 | Non-fatal warnings |
| `ERROR` | 6 | Error messages |
| `ANSWER` | 7 | Final answers to the user |
| `QUESTION` | 8 | User messages (displayed as questions in the conversation) |
| `REQUEST_INPUT` | 9 | Agent requests user input, optionally with structured UX options |
| `IDLE` | 10 | Agent is idle, waiting for input |
| `TERMINATED` | 11 | Agent has been terminated |
| `STREAMING_CHUNK` | 12 | Real-time LLM token streaming |
| `BATCH_PROGRESS` | 13 | Progress updates for batch tool executions |

## Message Formats

The system uses two message formats for different purposes:

- **`CompactMessage`** -- the **wire format**, optimized for bandwidth (~85% smaller). Used between server and client over SSE/WebSocket.
- **`AgentMessage`** -- the **UI format**, with readable field names. Used by UI components and application code.

The client SDK converts wire messages to `AgentMessage` automatically, so application code always works with the friendlier format.

### CompactMessage (wire format)

Used over the wire between server and client. You should not need to work with this directly.

```ts
interface CompactMessage {
    t: AgentMessageType;  // Message type
    m?: string;           // Message content
    w?: string;           // Workstream ID (omitted when "main")
    d?: unknown;          // Type-specific details
    f?: 0 | 1;           // Is final chunk (streaming only)
    ts?: number;          // Timestamp
    i?: string;           // Activity ID (for deduplication)
}
```

### AgentMessage (UI format)

The format used by UI components and application code. This is what you receive in `streamMessages` callbacks and what `ModernAgentConversation` works with internally.

```ts
interface AgentMessage {
    timestamp: number;
    workflow_run_id: string;
    type: AgentMessageType;
    message: string;
    details?: any;
    workstream_id?: string;
}
```

### Conversion Utilities

The `@vertesia/common` package provides utilities for working with both formats:

- `parseMessage(data)` -- Accepts string or object, returns `CompactMessage` regardless of input format
- `toAgentMessage(compact, runId)` -- Converts wire format to UI format (done automatically by the client SDK)
- `toCompactMessage(msg)` -- Converts UI format to wire format
- `createCompactMessage(type, options)` -- Convenience constructor for server-side code

## Real-Time Streaming

The client uses a **two-phase streaming strategy** for optimal performance:

1. **Phase 1 -- Historical fetch:** `GET /runs/{workflowId}/{runId}/updates` returns all past messages as gzip-compressed JSON. This is efficient because HTTP responses support compression while SSE streams do not.

2. **Phase 2 -- Real-time SSE:** `GET /runs/{workflowId}/{runId}/stream?skipHistory=true` opens an SSE connection for live updates only. The `since` parameter ensures no messages are lost between phases.

**Reconnection** is handled automatically with exponential backoff (1s base, 30s max, 10% jitter, up to 10 attempts).

### LLM Token Streaming

During LLM generation, tokens are batched into `STREAMING_CHUNK` messages at **16ms intervals or 200 characters** before being published to Redis. When the final `THOUGHT` or `ANSWER` message arrives with a matching `activity_id`, streaming chunks are replaced by the final message to avoid duplication.

## Workstreams

Messages carry a `workstream_id` (defaults to `"main"`). This enables parallel agent execution where multiple workstreams run concurrently. The conversation only closes when the **main** workstream sends `COMPLETE` or `TERMINATED` — other workstreams can complete independently.

For a comprehensive guide on the workstream system, see [Workstreams](/agent-runner/workstreams).

### Non-Blocking Execution Model

Workstreams are **non-blocking**: the parent agent launches a sub-agent via `launch_workstream` and immediately receives a `launch_id`. The child runs in the background as a separate Temporal workflow, communicating with the parent through signals.

The parent can continue working — launching more workstreams, using other tools, or waiting for results. When a child completes, the parent receives a system message with the result summary.

### Workstream Lifecycle

Each workstream moves through the following states:

| State | Description |
|-------|-------------|
| `running` | Sub-agent is actively executing |
| `canceling` | Termination requested, awaiting graceful shutdown (60s grace period) |
| `completed` | Finished successfully with a summary |
| `failed` | Encountered an unrecoverable error |
| `timeout` | Exceeded its deadline and was automatically cancelled |
| `canceled` | Terminated by the parent via `terminate_workstream` |

### Progress Reporting

Running workstreams report progress through phases:

| Phase | Description |
|-------|-------------|
| `planning` | Sub-agent is analyzing the task and forming a plan |
| `executing_tool` | Currently running a tool (tool name included) |
| `synthesizing` | Combining results and forming a response |
| `blocked` | Waiting for input or a dependency |
| `done` | Finished processing |

Progress updates include the current iteration number, a message from the model, and the percentage of deadline elapsed.

### Deadline Management

- **Default deadline:** 5 minutes
- **Maximum deadline:** 30 minutes (configurable via `deadline_seconds`)
- **Minimum deadline:** 30 seconds
- Warnings are sent to the parent at **75%** and **90%** of the deadline
- At **80%** of the deadline, the child automatically receives a wrap-up steering directive
- If the deadline is exceeded, the workstream is terminated and the parent receives a timeout notification

### Artifact Merging

By default (`merge_child_artifacts: true`), artifacts created by a child workstream in its `out/` and `files/` directories are automatically merged back to the parent's artifact space. Artifacts are namespaced by child run ID to avoid conflicts (e.g., `files/{child_id}/output.json`).

### Interactive Workstreams

When launched with `interactive: true`, a workstream enters interactive mode: after completing its initial task, it waits for follow-up messages sent via `message_workstream`. This enables multi-turn conversations with specialist sub-agents (used, for example, in the Expert Roundtable pattern).

Interactive workstreams **never complete on their own** — the parent must explicitly call `terminate_workstream` to close them.

### Steering

The parent can send mid-execution directives to a running workstream via `steer_workstream` without terminating it:

| Command | Description |
|---------|-------------|
| `add_instruction` | Add new instructions to the sub-agent |
| `set_priority` | Change the priority or focus of the task |
| `add_constraint` | Add constraints to narrow scope |
| `pause_requested` | Request the sub-agent to pause |
| `resume_requested` | Request the sub-agent to resume |

## Embedding the Conversation UI

The `ModernAgentConversation` component from `@vertesia/ui` is the **recommended way** to display agent conversations in your application. It handles all the complexity of message streaming, reconnection, plan visualization, file uploads, and message rendering.

Do not implement raw message streaming yourself. The `ModernAgentConversation` component handles streaming, chunk aggregation, deduplication, reconnection, plan extraction, optimistic updates, and many edge cases that are difficult to replicate correctly.

### Basic Usage

Display an existing agent conversation:

```tsx
import { ModernAgentConversation } from "@vertesia/ui/features";

function MyAgentView({ workflowId, runId }) {
    return (
        <ModernAgentConversation
            run={{ workflow_id: workflowId, run_id: runId }}
            interactive={true}
        />
    );
}
```

### Starting a New Conversation

Provide a `startWorkflow` callback to let users initiate conversations. Use the [Agent Runs API](/api/agents) (`store.agents.start()`) to create the run:

```tsx
import { ModernAgentConversation } from "@vertesia/ui/features";
import { useUserSession } from "@vertesia/ui/session";

function MyAgentView() {
    const { store } = useUserSession();

    const startWorkflow = async (message?: string) => {
        const run = await store.agents.start({
            interaction: "MyAgent",
            data: { task: message },
            interactive: true,
        });
        return {
            run_id: run.first_workflow_run_id,
            workflow_id: run.workflow_id,
        };
    };

    return (
        <ModernAgentConversation
            startWorkflow={startWorkflow}
            startButtonText="Start Agent"
            placeholder="Describe your task..."
            interactive={true}
        />
    );
}
```

### Component Properties

        An existing workflow run to display. When provided, the component connects to the message stream and renders the conversation. Use `first_workflow_run_id` and `workflow_id` from the AgentRun object.
     Promise<{ run_id, workflow_id }>">
        Callback to start a new workflow. When provided without `run`, the component renders a start view with a message input. The returned IDs are used to connect to the new conversation. Use `store.agents.start()` to create the run.
        Whether users can send messages to the agent. When `false`, user input is only shown when the agent sends a `REQUEST_INPUT` message.
        Title displayed in the conversation header. Defaults to the workflow ID.
        Placeholder text for the message input field.
        Label for the button that starts a new conversation.
        An initial message displayed above the start view to give context to the user.
     void" modifier="optional">
        Called when the user clicks the close button.
        Adjusts layout for modal display (narrower max-width, different close button placement).
        When `true`, the conversation area uses full width instead of a centered max-width layout.
     void" modifier="optional">
        Callback to reset the workflow. When provided, a reset button is shown in the header.
        Completely hide the user input area.

### File Upload Support

The component supports drag-and-drop and button-based file uploads. Files are uploaded to the workflow's artifact storage and the agent is signaled via `FileUploaded`.

```tsx
<ModernAgentConversation
    run={run}
    acceptedFileTypes=".pdf,.doc,.docx,.txt,.csv,.png,.jpg"
    maxFiles={10}
/>
```

     void" modifier="optional">
        Custom handler for file selection. If omitted, the component handles uploads internally using artifact storage.
        External file upload state to display in the input area.
     void" modifier="optional">
        Called when the user removes an uploaded file.
        Accepted MIME types / extensions for file uploads.
        Maximum number of files that can be uploaded simultaneously.
        Disables the send/start buttons while files are uploading.

### Document Search Integration

You can integrate a custom document search UI using a render prop:

```tsx
<ModernAgentConversation
    run={run}
    renderDocumentSearch={({ isOpen, onClose, onSelect }) => (
        <MyDocumentSearch
            isOpen={isOpen}
            onClose={onClose}
            onSelect={onSelect}
        />
    )}
    selectedDocuments={selectedDocs}
    onRemoveDocument={(docId) => removeDoc(docId)}
/>
```

### Context and Attachments

     string[]" modifier="optional">
        Returns document IDs to include as `store:` references in messages.
     void" modifier="optional">
        Called after attachments are sent, allowing you to clear the attachment list.
     Record<string, unknown>" modifier="optional">
        Returns additional metadata to include in every user signal. Useful for passing context like entity IDs.

### Fusion Fragment Support

When building data-driven applications, you can provide data for `fusion-fragment` code blocks in agent responses:

```tsx
<ModernAgentConversation
    run={run}
    fusionData={{
        fundName: "Tech Growth IV",
        vintage: 2024,
        totalCommitments: 500000000,
    }}
/>
```

### Styling

        Additional Tailwind classes for the input container.
        Additional Tailwind classes for the input field.

## SDK-Only Integration

If you cannot use the React component (e.g., in a Node.js backend or non-React frontend), use the `WorkflowsApi` directly from `@vertesia/client`. See the [SDK page](/agent-runner/sdk) for examples of `streamMessages` and `sendSignal`.

The SDK approach requires you to handle message parsing, deduplication, streaming chunk aggregation, and reconnection yourself. Prefer the `ModernAgentConversation` component whenever possible.

## Key Source Files

| Component | Package | Path |
|-----------|---------|------|
| Message types | `@vertesia/common` | `composableai/packages/common/src/store/workflow.ts` |
| Conversation state | `@vertesia/common` | `composableai/packages/common/src/store/conversation-state.ts` |
| Signals | `@vertesia/common` | `composableai/packages/common/src/store/signals.ts` |
| Client streaming API | `@vertesia/client` | `composableai/packages/client/src/store/WorkflowsApi.ts` |
| Conversation component | `@vertesia/ui` | `composableai/packages/ui/src/features/agent/chat/ModernAgentConversation.tsx` |

---

## Overview

Source: https://docs.vertesiahq.com/agent-runner/overview
Markdown: https://docs.vertesiahq.com/llms/agent-runner/overview.md

`Agent Runner` is Vertesia's core framework for building powerful AI agents.

So, what exactly is an AI agent? Imagine a super-smart system driven by a Large Language Model (LLM). This LLM acts as the agent's brain, making decisions and choosing the right actions. The agent then performs these actions, gets feedback, and uses it to figure out if it needs to do more or if the job's done! It's all about intelligent, self-directed task completion! For exactly how a run executes – the decide, act, observe, repeat cycle, tool dispatch, and how a run terminates – see [Execution Model](/agent-runner/execution-model).

![Agent Runner Conversation Overview](/agents/agent-conversation-overview.png)

Inside Every Agent:

* Large Language Model: The brains of the operation, handling all the logic and decision-making.
* Tools: These are the agent's hands and feet, allowing it to interact with the outside world and perform specific tasks.
* Agent Executor: This is the conductor, managing the agent's actions and ensuring everything runs smoothly.

What makes Agent Runner so powerful?

It leverages Vertesia's robust LLM interaction framework and sophisticated workflow engine. This means you can easily configure, run, and orchestrate complex AI agents, seamlessly integrating them into your existing processes.

In this documentation, we'll guide you step-by-step through the process:

* Getting Started in Studio: You'll learn how to launch and interact with your very first AI agent right within the intuitive Studio environment.
* Crafting Specialized Agents: Discover how to configure and fine-tune AI agents for specific, powerful tasks.
* Seamless Application Integration: Integrate your custom AI agents directly into your applications using the Vertesia SDK for TypeScript, making them a core part of your solutions
* Built-in Tools and Skills: Explore the catalog of built-in tools and reusable skills that agents can use to interact with data, documents, and external systems.

To learn more about built-in tools, see the [Built-in Tools](/agent-runner/tools) page. For reusable, code-heavy capabilities exposed as tools (such as data analysis or automation helpers), see [Agent Skills](/agent-runner/skills).

---

## SDK Integration

Source: https://docs.vertesiahq.com/agent-runner/sdk
Markdown: https://docs.vertesiahq.com/llms/agent-runner/sdk.md

In previous sections, we've seen how the **Agent Runner** provides an excellent environment for quickly experimenting with Agents in Studio. To seamlessly integrate Vertesia Agents into your business applications and workflows, Vertesia offers a comprehensive REST API and a robust [TypeScript SDK](/quickstart#vertesia-sdk).

This section will guide you through interacting with agents using the TypeScript SDK. No complex configuration is required to get started.

## Client Initialization

First, you'll need to get an instance of the Vertesia client. Remember to replace `apiKey` with your actual API key.

```js
import { VertesiaClient } from "@vertesia/client";
import { AgentMessageType } from "@vertesia/common";

const vertesia = new VertesiaClient({
  site: 'api.vertesia.io',
  apikey: '<YOUR_API_KEY>',
});
```

## Initiating Agent Execution

Start an agent run using the `store.agents.start()` method. This creates a stable `AgentRun` with its own ID, lifecycle tracking, and artifact storage.

```js
const task = `
Generate three lease agreement documents for commercial office space, each tailored to different specifications:

- A basic, cost-effective option suitable for approximately eight workstations.
- A mid-range option accommodating approximately twenty workstations, three meeting rooms, and two individual phone booths.
- A premium option located on the fortieth floor or above in an office building, designed to accommodate approximately fifty workstations, ten meeting rooms, and ten individual phone booths.

A lease agreement document must have the following metadata properties:
- space size in square feet
- term length
- monthly cost
- deposit amount
- state
- city
- address
- property manager

If a document type named  "Lease Agreement" doesn't already exit, create it with properties listed above

Subsequently, these documents should be organized and stored within a collection named "Office Leases."
`;

const run = await vertesia.store.agents.start({
  interaction: "MultipurposeAgent",
  data: {
    task: task
  },
  interactive: true,
});

console.log(run.id); // stable AgentRun ID
```

## Streaming Agent Messages

You can then stream messages from the agent using the `streamMessages` function. This function uses the AgentRun ID returned in the previous step.

```js
await vertesia.store.agents.streamMessages(run.id, (item) => {
  const { type, message } = item;
  const date = new Date(item.timestamp);
  console.log(`${date.toLocaleString()} - ${type}:\n${message}\n`);
});
```

The `streamMessages` function automatically stops when a message of type `AgentMessageType.COMPLETE` is received. This signals that the agent's current task is finished.

## Sending Signals to the Agent

Finally, to send a message or "signal" to the agent, use the sendSignal function.

```js
await vertesia.store.agents.sendSignal(run.id, "UserInput", {
  message: "create a spreadsheet with the agreement properties and add it to the collection",
});
```

In the current architecture, agents handle this kind of spreadsheet workflow by orchestrating skills together with the `execute_shell` and artifact tools, rather than using dedicated spreadsheet-specific built-in tools.

## Managing Agent Lifecycle

The Agent Runs API also supports lifecycle operations:

```js
// Retrieve the current state of an agent run
const current = await vertesia.store.agents.retrieve(run.id);
console.log(current.status); // 'running' | 'completed' | 'failed' | 'cancelled'

// Terminate a running agent
await vertesia.store.agents.terminate(run.id, 'No longer needed');

// Restart a completed/failed agent (continues the same conversation)
const restarted = await vertesia.store.agents.restart(run.id);

// Fork into a new agent run (new ID, loads history from source)
const forked = await vertesia.store.agents.fork(run.id);
```

## Further Exploration

- Full [Agent Runs API reference](/api/agents)
- A simple agent cli example is available on [GitHub](https://github.com/vertesia/examples/tree/main/agent-cli)

---

## Skills

Source: https://docs.vertesiahq.com/agent-runner/skills
Markdown: https://docs.vertesiahq.com/llms/agent-runner/skills.md

Skills are reusable, curated capabilities that combine remote tools with ready-to-use code, such as data analysis scripts or automation helpers. They are exposed to agents as tools whose names start with the `learn_` prefix and are executed inside the Daytona sandbox alongside the built-in tools.

When an agent uses a skill tool, Vertesia:

- Tracks the skill in the conversation state, including its name, source URL, language, and required packages.
- Automatically syncs the skill's scripts into the Daytona sandbox under `/home/daytona/skills/{skill_name}/`.
- Installs any language and system packages declared by the skill so they are available for subsequent `execute_shell` calls.

This makes skills ideal for encapsulating complex logic (for example, spreadsheet analysis or ETL pipelines) without exposing their internal implementation to the model.

## How Skills Work with Agents

Skills are discovered and registered as part of the tools available to the agent. From the model's perspective they behave like normal tools:

- The tool name has the form `skill__` (for example, `skill_data_analysis_analyze_spreadsheet`).
- The tool input schema defines the parameters the model must provide.
- The tool response can include both content and artifacts for downstream processing.

When a skill tool is executed:

1. The agent invokes the skill by name (for example, `skill_data_analysis_analyze_spreadsheet`).
2. The remote tools infrastructure (see [Custom Tools](/agent-runner/custom-tools)) runs the skill and returns a result.
3. Vertesia records the skill usage in the conversation state and syncs its scripts into the Daytona sandbox.
4. Subsequent `execute_shell` calls can import and reuse those scripts from `/home/daytona/skills/{skill_name}/`, with any declared packages already installed.

This pattern lets you keep heavy lifting (for example, Python + pandas data analysis) in versioned skill collections while the LLM focuses on orchestration.

## Typical Skill Use Cases

Common categories of skills include:

- **Data analysis skills** – Analyze, transform, and summarize structured data or spreadsheets using Python and popular libraries.
- **Data platform skills** – Create databases, import data, run SQL queries, and build Vega-Lite dashboards. See the [Data Platform Skills Reference](/data-platform/skills) for details.
- **Code and scripting skills** – Execute shell automation or TypeScript scripting tasks that are too complex or specialized to express directly via prompts.
- **Document processing skills** – Implement advanced parsing, enrichment, or extraction workflows over documents fetched from the knowledge base.
- **Math and utility skills** – Provide robust numerical and unit-conversion helpers beyond what the base model offers reliably.

Examples from the built-in skills server include:

- `data-analysis/analyze-data`, `etl-pipeline`, `analyze-spreadsheet`, and `create-spreadsheet` for Python + pandas workflows.
- `data-analysis/make-chart` for teaching the model how to emit chart JSON specs inside ` ```chart ` code blocks that the UI renders as interactive charts.
- `document/zip-archives` for working with zipfiles whose text rendition is an index of contained files, showing how to download the `.zip` into the sandbox, unzip it with `unzip`, and then analyze the extracted contents with other skills.

From the agent's perspective, each of these skills is just another tool that can be chained with built-in tools such as `search_documents`, `fetch_document`, `write_artifact`, and `execute_shell`.

## System Skills

Vertesia ships with a set of **system skills** — built-in capabilities that come pre-installed on the platform. System skills follow the `learn_*` naming pattern: when an agent calls `learn_<skill_name>`, it receives detailed instructions and unlocks the skill's related tools.

System skills are different from custom skills hosted on external tool servers. They are always available and do not require sandbox setup or package installation.

### How System Skills Work

1. The agent sees a `learn_*` tool in its available tools list (e.g., `learn_workstreams`).
2. The agent calls the skill tool when it needs that capability.
3. Vertesia returns detailed instructions and best practices for the skill.
4. The skill's related tools are unlocked and become available for use.

This progressive disclosure pattern keeps the agent's initial tool list manageable while making advanced capabilities available on demand.

### Available System Skills

| Skill Tool | Title | Description | Unlocked Tools |
|------------|-------|-------------|----------------|
| `learn_workstreams` | Workstreams | Launch and manage parallel sub-agents that run independently | `launch_workstream`, `check_workstream`, `terminate_workstream`, `steer_workstream`, `message_workstream`, `list_workstreams`, `get_workstream_result` |
| `learn_expert_roundtable` | Expert Roundtable | Run multi-perspective debates with specialist sub-agents using different models | `launch_workstream`, `check_workstream`, `steer_workstream`, `message_workstream`, `list_workstreams`, `get_workstream_result`, `terminate_workstream`, `analyze_conversation` |
| `learn_agent_scheduling` | Agent Scheduling | Schedule agents for one-time or recurring execution | `schedule_agent`, `schedule_recurring_agent` |
| `learn_content_authoring` | Document Authoring | Create, compose, and export documents with PDF/DOCX rendering | `render_markdown`, `render_artifact_officexml`, `preview_artifact_officexml`, `read_artifact_xml`, `edit_artifact_docx`, `inspect_pdf`, `merge_artifacts` |
| `learn_docx_editing` | DOCX Editing | Edit DOCX files using structured XPath edits | `read_artifact_xml`, `edit_artifact_docx`, `render_artifact_officexml`, `preview_artifact_officexml`, `inspect_pdf`, `list_artifacts` |
| `learn_pptx_editing` | PPTX Editing | Edit PowerPoint PPTX files using structured XPath edits | `read_artifact_xml`, `edit_artifact_pptx`, `render_artifact_officexml`, `preview_artifact_officexml`, `inspect_pdf`, `list_artifacts` |
| `learn_document_management` | Document Management | Source-based document creation, updates, revision history, and document merging | `create_document`, `update_document`, `list_revisions`, `diff_revisions`, `merge_documents` |
| `learn_conversation_analysis` | Conversation Analysis | Search and analyze past conversations with AI | `search_conversations`, `analyze_conversation` |
| `learn_image_analysis` | Image Analysis | Analyze and transform images using ImageMagick | `analyze_image` |
| `learn_presentation_authoring` | Presentation Authoring | Create slide decks with markdown, custom SVG slides, and PDF export | `render_slides` |
| `learn_send_email` | Send Email | Send emails with markdown formatting and inline images | `send_email` |
| `learn_document_search` | Document Search | Advanced search with full-text, vector, and DSL queries | `search_documents` |

## Authoring Custom Skills

Skills are hosted by tool servers built with the Vertesia tools SDK and registered as tool collections on applications. The same mechanisms used for [Custom Tools](/agent-runner/custom-tools) apply:

- A tool server exposes a set of tools and skills over HTTP or MCP.
- Each skill includes metadata about its scripts, language, and required packages.
- When the agent uses a skill, Vertesia uses this metadata to sync scripts and install dependencies into the sandbox.

For a practical starting point, see the Vertesia tools server examples and the `@vertesia/tools-sdk` documentation used by the platform's own skills.

---

## How Skills Work

Source: https://docs.vertesiahq.com/agent-runner/skills-model
Markdown: https://docs.vertesiahq.com/llms/agent-runner/skills-model.md

Agents with fifty tools available from turn one don't behave well. They forget the rules, misuse the wrong tool, hallucinate arguments. **Skills** are the mechanism that fixes this: a skill is a bundle of *instructions* + a *tool set* that the agent must explicitly opt into.

This page explains the skill model: what a skill is, how agents invoke skills, and how skills differ from plain tools.

## The shape

Every system skill is a `SystemSkillDefinition`:

```ts
interface SystemSkillDefinition {
    name: string;                                 // e.g. "web_search"
    title: string;                                // "Web Search"
    description: string;                          // shown in the skill catalog
    instructions: string | ((ctx) => string);     // unlocked on call
    tools: string[];                              // unlocked on call
    related_tools?: string[];                     // complementary, shown in catalog
    input_schema?: ToolDefinition['input_schema'];
}
```

Each skill is surfaced to agents as a tool named `learn_` — so `web_search` becomes `learn_web_search`. The tool's description is `[Skill] `; its input schema is either a generic `{ context: string }` or whatever the skill declares.

## Two phases: unlock, then act

Without a skill, an agent sees only its declared tools plus any `learn_*` entries. Domain tools — `web_search_serper`, `fetch_document`, `create_or_update_object_type` — aren't available yet. When the agent calls a `learn_*`:

1. The skill's `tools` list is **unlocked** for the remainder of the conversation. They become callable on subsequent turns.
2. The skill's `instructions` (markdown, often several paragraphs) are returned as the tool result.
3. The agent reads the instructions, then proceeds with the newly-available tools.

The forced read matters. Instructions are where the platform encodes "when to use provider A vs B", "which parameter is required", "common pitfalls". Without the unlock, an agent can't stumble into calling a misconfigured tool — it has to ask for the skill first and reads the rules on the way in.

This is why you'll often see agent prompts say things like *"if you need to search the web, call `learn_web_search` first"*. It's not a politeness; it's the only path that opens the tools.

## Static vs dynamic instructions

Instructions can be a string literal or a function of `SkillContext`:

```ts
interface SkillContext {
    project?: Project;
    enabledTools: string[];
}
```

Dynamic instructions let a skill adapt to the project — for example, `web_search` can inspect `project.configuration.web_search_providers` and only describe Serper's behavior if Serper is actually configured. The unlocked `tools` list is also typically filtered so the agent doesn't see tools that would fail at runtime.

## `related_tools`

A skill's `tools` are the ones **unlocked by calling it**. `related_tools` are tools that complement it but stay hidden — typically rare or heavy tools the skill itself points at conditionally. They show up in the skill catalog (`list_tools`) so an agent can see what's available without unlocking everything at once.

## System skills live in `packages/workflows/src/skills/sys/`

Browse the directory for concrete examples. A few worth reading:

- **`web-search.ts`** — classic multi-provider skill. Describes three search providers and three fetch providers, returns a comparison table, unlocks all six tools.
- **`process-definitions.ts`** — the grammar + authoring rules for process definitions. Unlocks `list_tools`, `list_interactions`, `validate_process_definition`, `create_process_definition`, etc. Used by the Studio Assistant for process authoring.
- **`document-search.ts`** — guidance on using `search_documents` in Search Mode vs DSL Mode.
- **`artifact-operations.ts`** — reading and writing agent-run artifacts.

Registration happens in `packages/workflows/src/skills/sys/index.ts`.

## How to declare which skills an agent gets

Skills are tools with a `+` prefix convention, same as any other tool:

```ts
agent_runner_options: {
    is_agent: true,
    tool_names: [
        "+learn_web_search",
        "+learn_document_management",
        "+fetch_document",     // unlocked already, no learn step
    ],
},
```

- `+learn_X` **adds** the skill's learn entry to the default tool set.
- `learn_X` (no prefix) **replaces** the default set with an explicit list.
- `-learn_X` removes a skill the defaults would otherwise include.

For process agent nodes, declare skills the same way in `node.tools`:

```json
"extract_terms": {
    "type": "agent",
    "tools": ["+learn_document_management", "+fetch_document"],
    ...
}
```

## The skill catalog

The `list_tools` builtin returns every tool *and* every skill (annotated as `is_system_skill: true`) that's available in the current project. An agent can call `list_tools` with a grep filter to discover capabilities before unlocking anything. This is how the Studio Assistant browses its own toolkit at runtime.

## Custom skills

Custom skills follow the same shape but are authored outside `packages/workflows/src/skills/sys/`. They're typically written as a `SkillDefinition` in the `tools-sdk` pattern (a markdown file with frontmatter plus an `instructions` block). The `toSystemSkill()` helper in `skills/types.ts` converts that shape into the runtime `SystemSkillDefinition`. See the tools-sdk docs for the authoring path.

## When to reach for a skill vs just a tool

Make it a tool when:

- The tool is always safe to call without context.
- One-liner usage; no preambles needed.
- It's a primitive the rest of the system composes freely (e.g. `get_current_time`).

Make it a skill when:

- There's non-trivial guidance — "use A for X, B for Y", pitfalls, required param combinations.
- The feature set is broad enough that loading everything at once bloats the tool list and hurts model quality.
- You want to enforce a particular ordering ("read the rules before building").

## When skills misfire

Two common failure modes:

1. **Agent never calls the skill.** Usually the prompt didn't mention the skill or didn't explain the forcing function. Fix in the system prompt: state that certain capabilities require `learn_*` first.
2. **Agent calls the skill repeatedly.** Once unlocked, a skill stays unlocked for the conversation — there's no need to re-call. If the agent loops, check that the instructions don't imply re-reading on every use.

## See also

- [Studio Assistant](/studio/assistant) — the always-on assistant that is itself skill-driven.
- [Authoring Processes](/processes/authoring) — the Studio Assistant's skill-first iteration loop for process authoring.
- [Agent Runner — Skills](/agent-runner/skills) — how to list, configure, and invoke skills on an agent run.
- [Agent Runner — Built-in Tools](/agent-runner/tools) — the broader tool catalog skills unlock.

---

## Built-in Tools

Source: https://docs.vertesiahq.com/agent-runner/tools
Markdown: https://docs.vertesiahq.com/llms/agent-runner/tools.md

This page documents all the built-in tools available in Vertesia Studio for use with Agents. For reusable, code-centric capabilities that are packaged and exposed as tools (such as spreadsheet analysis or ETL pipelines), see also [Agent Skills](/agent-runner/skills).

## Core Tools

Fundamental tools for reasoning, planning, and task organization. These are the essential building blocks for complex agent workflows.

### Think Tool

**Name:** `think`

A tool for deep thinking and analysis of complex problems step by step. Useful for brainstorming and planning.

### Plan Tool

**Name:** `plan`

Creates structured, executable plans with tracked progress.

### Update Plan Tool

**Name:** `update_plan`

Updates multiple tasks in your active plan simultaneously with visual progress tracking. Works in conjunction with the plan tool to maintain live status updates.

## Workstream Tools

Non-blocking tools for launching and managing parallel sub-agents (workstreams). Each workstream runs independently in the background while the parent agent continues working. See [Workstreams](/agent-runner/workstreams) for a full conceptual guide.

### Launch Workstream Tool

**Name:** `launch_workstream`

Launches a dedicated parallel workstream (sub-agent) to independently solve a specific part of a complex problem. Returns immediately with a `launch_id` — the sub-agent runs in the background while you continue working.

**Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `id` | string | Yes | Short identifier for this workstream (ASCII, underscores for spaces) |
| `name` | string | Yes | Human-readable name for tracking |
| `instruction` | string | Yes | Clear, detailed instructions for what the sub-agent should accomplish |
| `allowed_tools` | string[] | Yes | Tools the sub-agent is allowed to use |
| `context` | object | No | Additional information or data to help the sub-agent |
| `merge_child_artifacts` | boolean | No | Merge child's `out/` and `files/` back to parent (default: `true`) |
| `model` | string | No | Model override — if omitted, inherits the parent's model |
| `deadline_seconds` | integer | No | Custom deadline in seconds (default: 300, max: 1800) |
| `interactive` | boolean | No | If `true`, workstream waits for follow-up messages via `message_workstream` (default: `false`) |

### Check Workstream Tool

**Name:** `check_workstream`

Check the status and progress of a running workstream by its `launch_id`. Returns current status, elapsed time, remaining time, latest progress, and deadline percentage. Also works for completed workstreams.

**Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `launch_id` | string | Yes | The `launch_id` returned when the workstream was started |

### List Workstreams Tool

**Name:** `list_workstreams`

List all workstreams (running and completed) with their status, duration, and latest progress. Useful for monitoring and debugging.

**Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `status_filter` | string | No | Filter by status: `all` (default), `running`, `completed`, `failed`, `timeout`, `canceled` |

### Terminate Workstream Tool

**Name:** `terminate_workstream`

Terminate a running workstream. Sends a cancellation request to the child workflow. Particularly important for interactive workstreams, which never complete on their own.

**Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `launch_id` | string | Yes | The `launch_id` of the workstream to terminate |

### Steer Workstream Tool

**Name:** `steer_workstream`

Send a mid-execution steering directive to a running workstream to adjust its behavior without terminating it.

**Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `launch_id` | string | Yes | The `launch_id` of the workstream to steer |
| `command` | string | Yes | Directive type: `add_instruction`, `set_priority`, `add_constraint`, `pause_requested`, or `resume_requested` |
| `message` | string | Yes | The directive message or instruction to send |

### Message Workstream Tool

**Name:** `message_workstream`

Send a follow-up message to an interactive workstream (one launched with `interactive: true`). Enables multi-turn conversations with a specialist sub-agent.

**Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `launch_id` | string | Yes | The `launch_id` of the interactive workstream |
| `message` | string | Yes | The message to send — the sub-agent receives this as user input and responds |

### Get Workstream Result Tool

**Name:** `get_workstream_result`

Retrieve the full result of a completed workstream. Returns the summary, status, duration, error information, and last progress details.

**Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `launch_id` | string | Yes | The `launch_id` of the completed workstream |

## Document Management Tools

Tools for managing documents in the Vertesia knowledge base. These tools handle CRUD operations for documents with full metadata support.

### Search Documents Tool

**Name:** `search_documents`

A powerful tool for searching and analyzing documents with two distinct modes: **Search Mode** for high-level queries and **DSL Mode** for direct Elasticsearch access.

#### Search Mode (Recommended)

Use Search Mode for most document searches. It provides a high-level API with automatic processing:

**Query Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `query.name` | string | Partial name match (autocomplete-style) |
| `query.type` | string | Filter by document type ID |
| `query.status` | string | Filter by document status |
| `query.full_text` | string | Full-text search with stemming and fuzzy matching |
| `query.vector` | object | Vector similarity search (see below) |
| `query.weights` | object | Weights for hybrid search (e.g., `{ full_text: 2, vector: 3 }`) |
| `query.score_aggregation` | string | Score aggregation method: `rrf`, `rsf`, or `smart` |
| `query.dynamic_scaling` | string | Dynamic weight scaling: `on` or `off` |

**Vector Search Options:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `query.vector.text` | string | Text to embed and search semantically |
| `query.vector.objectId` | string | Reuse embeddings from an existing object |
| `query.vector.image` | string | Image URL or base64 for vision embedding |
| `query.vector.config` | object | Embedding types to use (`text`, `properties`, `vision`, `code`) |

**Example - Hybrid Search:**

```json
{
  "query": {
    "full_text": "quarterly financial report",
    "vector": { "text": "company earnings analysis" },
    "weights": { "full_text": 2, "vector": 3 },
    "score_aggregation": "smart"
  },
  "limit": 20
}
```

#### DSL Mode (Power Users)

Use DSL Mode for direct Elasticsearch Query DSL access. Ideal for analytics, complex aggregations, and full control:

**DSL Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `dsl.query` | object | Elasticsearch query clause (e.g., `match_all`, `term`, `bool`) |
| `dsl.aggs` | object | Aggregations for analytics (e.g., `terms`, `date_histogram`) |
| `dsl.size` | number | Results to return (0-10,000; use 0 for aggregations-only) |
| `dsl.from` | number | Pagination offset (0-100,000) |
| `dsl.sort` | array | Sort order (e.g., `[{ "created_at": "desc" }]`) |

**Example - Aggregation:**

```json
{
  "dsl": {
    "aggs": {
      "by_status": { "terms": { "field": "status" } },
      "by_type": { "terms": { "field": "type.name" } }
    },
    "size": 0
  }
}
```

#### Shared Options (Both Modes)

| Parameter | Type | Description |
|-----------|------|-------------|
| `limit` | number | Maximum results (default: 100) |
| `offset` | number | Skip n documents for pagination |
| `format` | string | Output format: `json`, `csv`, or `table` |
| `count_only` | boolean | Return only document count |
| `all_revisions` | boolean | Include all revisions, not just latest |
| `collection_id` | string | Search within specific collection |
| `analyze` | boolean | Run LLM analysis on results |
| `analyzer_prompt` | string | Custom instructions for LLM analysis |
| `facets` | array | Compute aggregated counts (e.g., `[{ name: "types", field: "type.name" }]`) |
| `output_artifact` | object | Stream large results to artifact file |

For more details on search configuration, see [Search Configuration](/content/search).

### Fetch Document Tool

**Name:** `fetch_document`

Retrieves a specific document by its identifier. Supports multiple modes (full document, properties only, content, sections, instrumented views, or AI-powered analysis) and can optionally stream large results to a workspace artifact for downstream processing with other tools.

### Create Document Tool

**Name:** `create_document`

Persists an existing source reference as a new document. Author text or markdown as an artifact first, then call `create_document` with `source: "artifact:"`. Binary artifacts, HTTPS URLs, and cloud storage URLs are preserved as file documents.

### Update Document Tool

**Name:** `update_document`

Updates existing documents with new content or properties. For structured XPath-based editing of DOCX documents, use the dedicated `edit_artifact_docx` tool instead.

### Create Content Object Tool

**Name:** `create_content_object`

Creates persistent content objects from external locations such as HTTPS URLs or cloud storage (for example, `https://…`, `s3://…`, `gs://…`). You can attach custom metadata, tags, and an optional collection so that imported files become searchable and available for later analysis.

### Spreadsheet Workflows with Skills

Spreadsheet creation and analysis are implemented using a combination of document tools, skills, artifacts, and the Daytona sandbox rather than dedicated spreadsheet-specific built-ins:

- Use `search_documents` and `fetch_document` to locate and access spreadsheet files stored in the knowledge base.
- Use skills (for example, data-analysis skills) to generate or transform spreadsheets and to declare any required packages.
- Use `write_artifact` to create helper scripts and data files, and `execute_shell` to run those scripts inside the sandbox, reading from `/home/daytona/files` and writing results to `/home/daytona/out`.
- Use `read_artifact`, `list_artifacts`, and related tools to inspect outputs, and `create_document(source: "artifact:out/")` or `create_content_object` to persist final results.

This pattern replaces the legacy spreadsheet tools and gives agents more flexibility and control over how spreadsheet data is processed.

## Document Rendering and Artifact Tools

Tools for rendering documents to PDF/DOCX, visually previewing them, and performing structured edits on DOCX files. These tools are unlocked by the `learn_content_authoring` system skill.

### Render Markdown Tool

**Name:** `render_markdown`

Renders a markdown artifact to PDF or DOCX using the full Pandoc + XeLaTeX pipeline. Requires an `artifact_path` pointing to a markdown file in the artifact store — does **not** accept document IDs directly.

**Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `artifact_path` | string | Yes | Path to a markdown artifact (e.g. `files/report.md`) |
| `format` | string | No | Output format: `pdf` (default) or `docx` |
| `title` | string | No | Document title for metadata and output filename |
| `toc` | boolean | No | Include table of contents (default: `true`) |
| `template_path` | string | No | Custom LaTeX template file (artifact path, `artifact:`, or `store:` protocol) |
| `logo_path` | string | No | Custom logo file (artifact path, `artifact:`, or `store:` protocol) |
| `data_source` | string | No | Source for template data injection (`store:` or `artifact:`) |

Output is stored at `out/{title}.{pdf|docx}` in the artifact workspace.

### Render DOCX Tool

**Name:** `render_docx`

Exports a DOCX file to PDF or Markdown. Accepts either an artifact path or a document store ID as source.

**Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `docx_path` | string | One of | Artifact path to a DOCX file (e.g. `out/report.docx`) |
| `document_id` | string | One of | Document store ID of an existing DOCX |
| `format` | string | No | Output format: `pdf` (default) or `markdown` |
| `output_path` | string | No | Output artifact path (defaults to `out/.pdf` or `out/.md`) |
| `title` | string | No | Base name used when `output_path` is omitted |

### Inspect PDF Tool

**Name:** `inspect_pdf`

Visually inspects a PDF page by page. Supports an overview thumbnail grid (default) and a high-resolution detail mode for specific pages.

**Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `pdf_path` | string | One of | Artifact path to a PDF file (e.g. `out/report.pdf`) |
| `document_id` | string | One of | Document store ID of a PDF document |
| `pages` | number[] | No | 1-indexed page numbers for detail mode (max 4) |
| `from_page` | number | No | Overview mode: start page (default: 1) |
| `to_page` | number | No | Overview mode: end page (default: last) |
| `columns` | number | No | Overview mode: grid columns (default: 3, max: 4) |

### Edit DOCX Artifact Tool

**Name:** `edit_artifact_docx`

Applies structured XPath-based edits to a DOCX artifact's `word/document.xml`. Supports insert, replace, delete, append, and move operations with optional tracked changes.

**Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `path` | string | Yes | Path to the DOCX artifact (e.g. `out/report.docx`) |
| `structured_edits` | array | Yes | XPath edit operations to apply |
| `track_changes` | boolean | No | Emit Word tracked changes markup (default: `false`) |
| `author` | string | No | Author name for tracked changes |

Each edit in `structured_edits` has:

| Field | Type | Description |
|-------|------|-------------|
| `operation` | string | `insert_before`, `insert_after`, `replace`, `delete`, `append`, or `move_to` |
| `anchor_type` | string | `xpath` |
| `anchor_expr` | string | XPath expression targeting the element |
| `content` | string | New XML content (for insert/replace/append operations) |
| `target_anchor_expr` | string | Target XPath for `move_to` operations |
| `position` | string | `top`, `end`, `before`, or `after` (for `move_to`) |

### Merge Artifacts Tool

**Name:** `merge_artifacts`

Merges multiple artifact files into a single output file by concatenating them in order. Useful for assembling sectional documents (e.g., after authoring sections independently with workstreams).

**Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `paths` | string[] | Yes | Ordered list of artifact paths to concatenate |
| `output_path` | string | Yes | Destination artifact path for the merged output |
| `separator` | string | No | Content inserted between each file (default: `\n\n`) |

## Type Management Tools

Tools for managing object type definitions and schemas. These tools control the structure and validation rules for different types of objects in the system.

### Get Object Type Tool

**Name:** `get_object_type`

Retrieves details about specific object type definitions.

### Create or Update Type Tool

**Name:** `create_or_update_object_type`

Creates new or updates existing object type definitions.

## Collection Management Tools

Tools for organizing and grouping related documents into collections. Collections provide hierarchical organization and bulk operations on document sets.

### Create Collection Tool

**Name:** `create_collection`

Creates a new collection for organizing related documents. Collections act as containers that group documents together for easier management and access.

### Update Collection Tool

**Name:** `update_collection`

Modifies an existing collection's properties, such as name, description, or schema definition. This tool updates collection metadata without affecting the documents contained within it.

### Add to Collection Tool

**Name:** `add_to_collection`

Places one or more existing documents into a collection for organization and grouping. This tool establishes relationships between documents and collections, without modifying the documents themselves.

### Remove from Collection Tool

**Name:** `remove_from_collection`

Removes documents from a collection while preserving the documents themselves. This tool only breaks the association between documents and a collection; it does not delete the documents from the system.

### Get Collection Tool

**Name:** `get_collection`

Accesses detailed information about an existing collection, including its name, description, schema, and member documents. This tool retrieves the full definition of a collection along with metadata about contained documents.

### Search Collections Tool

**Name:** `search_collections`

Finds collections by searching for partial matches in collection names. This tool searches through all existing collections and returns those whose names contain the specified search term using case-insensitive partial matching.

## Temporary Artifact Tools

Tools for managing temporary artifacts in the agent workspace. Artifacts are per-run files (scripts, intermediate data, and outputs) that are automatically deleted when the workflow completes. Use these tools together with `execute_shell` for robust code and data workflows.

### Write Artifact Tool

**Name:** `write_artifact`

Writes a temporary file into the agent workspace. Use `type: "script"` for code (synced to `/home/daytona/scripts/`) or `type: "file"` for data (synced to `/home/daytona/files/`).

### Read Artifact Tool

**Name:** `read_artifact`

Reads the content of a temporary artifact, with optional line ranges and line numbers for precise inspection.

### List Artifacts Tool

**Name:** `list_artifacts`

Lists available artifacts, optionally filtered by a path prefix such as `scripts/`, `files/`, or `out/`.

### Grep Artifact Tool

**Name:** `grep_artifacts`

Searches for a regular-expression pattern across artifacts, useful for finding errors or specific content in generated files.

### Patch Artifact Tool

**Name:** `patch_artifact`

Applies literal find-and-replace edits inside an artifact, typically after inspecting it with `read_artifact` or `grep_artifacts`.

### View Image Tool

**Name:** `view_image`

Exposes an image from an artifact (for example, `out/plot.png`) or a stored Vertesia document as an image attachment that the model can see and combine with `analyze_image`.

## Web and External Tools

Tools for interacting with external services and executing custom code. These tools extend agent capabilities beyond the core platform functionality.

### Web Search Tool

**Name:** `web_search`

Searches the web for information using specified queries and options.

This activity requires an API key for [serper](https://serper.dev/). Go to Setting in Studio to configure your API key.

### Execute Shell Tool

**Name:** `execute_shell`

Executes shell commands inside a managed Daytona sandbox.

The sandbox is created on first use for a workflow run and reused across calls, preserving installed packages and files until the workflow completes.

Use this tool to:
- Run Python or other language scripts stored under `/home/daytona/scripts` (for example, data analysis with pandas).
- Manipulate files under `/home/daytona/files`, `/home/daytona/documents`, and `/home/daytona/out`.
- Install additional packages needed by skills using the tool input (for example, extra Python or system packages).

Artifacts created with the temporary artifact tools are automatically synced into the sandbox on each call:
- `scripts/*` → `/home/daytona/scripts/`
- `files/*` → `/home/daytona/files/`
- `skills/*` → `/home/daytona/skills/`
- `out/*` ↔ `/home/daytona/out/` for derived outputs that should be reused later.

You can also use the `documents` parameter to download Vertesia documents into `/home/daytona/documents/` (as original files or extracted text) before running shell commands that analyze them.

### Ask User Tool

**Name:** `ask_user`

Requests input from users during workflow execution.

### Analyze Image Tool

**Name:** `analyze_image`

Executes ImageMagick commands on images and PDFs, storing results to cloud storage. Supports standard image formats and PDF documents with command chaining capabilities.

Combine this with the `view_image` tool to first surface images from artifacts or stored documents into the conversation so the model can inspect and transform them.

## Conversation Tools

Tools for searching and analyzing past conversations and agent runs.

### Search Conversations Tool

**Name:** `search_conversations`

Searches workflow runs (conversations) by status, time range, initiator, or interaction name, with pagination support and an optional `output_artifact` setting to stream full result sets into an artifact while returning a small preview.

### Analyze Conversation Tool

**Name:** `analyze_conversation`

Loads the conversation from another workflow run and analyzes it using an `analyzer_prompt`, optionally constrained by a `result_schema`. Useful for reviewing agent behavior, extracting key outcomes, or monitoring progress of running workflows.

## Communication Tools

Tools for sending notifications and messages to external recipients.

### Send Email Tool

**Name:** `send_email`

Sends emails using the Resend email service. Accepts markdown content which is automatically converted to HTML with a plain text fallback. Supports email conversations where recipients can reply and have their responses routed back to the workflow.

**Project Integration Configuration:**

This tool reads its configuration from the project's Resend integration (configure in Project Settings > Integrations):

| Setting | Required | Description |
|---------|----------|-------------|
| `enabled` | Yes | Must be true for the tool to run |
| `api_key` | Yes | Your Resend API key |
| `email_domain` | Yes | Verified Resend domain used for both the from address and inbound reply routing |
| `default_from_name` | No | Display name for outgoing emails (defaults to `Vertesia - {project name}`) |
| `webhook_secret` | No | Required for receiving email replies via Resend webhooks |

The `from` address is constructed automatically from the integration settings and cannot be overridden; see **Email Reply Routing** below for when `reply-to` is set.

**Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `to` | string[] | Yes | Array of recipient email addresses |
| `subject` | string | Yes | The email subject line |
| `markdown` | string | Yes | The email body in markdown format |
| `cc` | string[] | No | Array of CC recipient addresses |
| `bcc` | string[] | No | Array of BCC recipient addresses |
| `attachments` | object[] | No | File attachments rendered as download links at the end of the email body. Each entry: `{ artifact_path? \| url?, filename, content_type? }` |
| `enable_reply` | boolean | No | Enable email reply routing. Defaults to true. Requires `email_domain` on the Resend integration. |

**Email Reply Routing:**

When `enable_reply` is true (default) and `email_domain` is configured on the Resend integration, the tool sends the email with a generated reply-to address in the format `r+{runId}@{email_domain}`. When a recipient replies:

1. The reply is received by Resend at the email domain
2. Resend sends a webhook to the Vertesia API
3. The webhook extracts the run ID and sends a `UserInput` signal to the workflow
4. The workflow receives the email content as user input and can respond

This enables email-based conversations with agents, where users can communicate via email instead of the UI.

**Inline artifact:// URLs:**

The only custom URL scheme resolved in the markdown body is `artifact://`. Matching links and image references are rewritten to signed download URLs before the markdown is converted to HTML:

- `![Chart](artifact://files/chart.png)` — renders as a remote `` with a signed URL
- `[Report](artifact://files/report.pdf)` — renders as a clickable link with a signed URL

Images are loaded via the recipient's mail client over HTTP at render time — they are not inlined as MIME/CID attachments. Explicit `attachments` entries are appended to the body as a bulleted list of download links rather than attached as files to the outbound email.

## Data Platform Tools

Tools for managing data stores, tables, queries, and dashboards. These tools enable agents to work with structured data using DuckDB databases.

For comprehensive documentation, see the [Data Platform Tools Reference](/data-platform/tools).

### Key Tools

- **data_get_schema** - Get the schema of a data store
- **data_list_tables** - List tables with metadata
- **data_create_database** - Create a new DuckDB database
- **data_create_tables** - Create tables atomically
- **data_import** - Import data from files or inline data
- **data_preview_dashboard** - Preview Vega-Lite dashboards
- **data_create_dashboard** - Create saved dashboards
- **data_render_dashboard** - Render dashboards to PNG

## Automation Tools

Tools for automating and scheduling recurring tasks.

### Schedule Workflow Tool

**Name:** `schedule_workflow`

Creates recurring schedules for agent/workflow execution using cron expressions. This tool is not enabled by default and must be explicitly added to an agent's tools.

**Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `name` | string | Yes | Name of the schedule for identification |
| `description` | string | No | Description of what this scheduled workflow does |
| `interaction` | string | Yes | The interaction/agent ID to execute on schedule |
| `cron_expression` | string | Yes | Cron expression defining when to run |
| `timezone` | string | No | Timezone for the cron expression (defaults to UTC) |
| `vars` | object | No | Variables to pass to the scheduled workflow |
| `enabled` | boolean | No | Whether to enable immediately (defaults to true) |

**Cron Expression Format:**

The cron expression uses 5 fields: `minute hour day month weekday`

| Field | Values | Description |
|-------|--------|-------------|
| minute | 0-59 | Minute of the hour |
| hour | 0-23 | Hour of the day |
| day | 1-31 | Day of the month |
| month | 1-12 or JAN-DEC | Month of the year |
| weekday | 0-6 or SUN-SAT | Day of the week (0=Sunday) |

Special characters:
- `*` - any value
- `,` - list separator (e.g., 1,3,5)
- `-` - range (e.g., 1-5)
- `/` - step (e.g., */15 for every 15)

**Common Cron Examples:**

| Expression | Description |
|------------|-------------|
| `0 9 * * *` | Every day at 9:00 AM |
| `0 9 * * MON` | Every Monday at 9:00 AM |
| `0 9 * * MON-FRI` | Weekdays at 9:00 AM |
| `0 0 1 * *` | First day of each month at midnight |
| `0 */2 * * *` | Every 2 hours |
| `30 8 * * *` | Every day at 8:30 AM |

**Important Notes:**
- Scheduled workflows run non-interactively but can still use `ask_user` for async input via email, webhooks, or headless UX listening to the stream
- Use meaningful names for easy identification in the UI
- Consider timezone when scheduling for specific business hours
- This tool must be explicitly added to an agent's tool list

## Index Configuration Tools

Tools for querying and managing search index configuration. These tools allow agents to inspect and update embedding settings and trigger reindexing operations.

### Get Index Configuration Tool

**Name:** `get_index_configuration`

Retrieves the current index status and configuration for the project's search infrastructure.

**Returns:**
- Index existence and health status
- Document count and storage size
- Embedding dimensions for text, image, and properties
- Field mappings and index version

**Example Response:**

```json
{
  "enabled": true,
  "exists": true,
  "index_name": "content_abc123_v3",
  "alias_name": "content_abc123",
  "document_count": 15234,
  "size_bytes": 52428800,
  "embedding_dimensions": {
    "text": 1536,
    "image": 1536,
    "properties": 1536
  },
  "field_mappings": {
    "text": "text",
    "updated_at": "date"
  },
  "version": 3
}
```

### Update Index Configuration Tool

**Name:** `update_index_configuration`

Updates index configuration settings, including embedding dimensions. Can trigger reindexing when configuration changes require it.

**Parameters:**

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `embedding_dimensions` | object | No | New dimensions for embedding types |
| `embedding_dimensions.text` | number | No | Dimensions for text embeddings |
| `embedding_dimensions.image` | number | No | Dimensions for image embeddings |
| `embedding_dimensions.properties` | number | No | Dimensions for properties embeddings |
| `force_reindex` | boolean | No | Trigger a full reindex of all documents |
| `user_confirmed` | boolean | Yes | Must be `true` - requires confirmation via `ask_user` first |

**Important:** This tool requires user confirmation before making changes. Always use `ask_user` to confirm the operation before calling this tool with `user_confirmed: true`.

**Example:**

```json
{
  "embedding_dimensions": {
    "text": 3072
  },
  "force_reindex": true,
  "user_confirmed": true
}
```

For more details on index configuration, see [Search Configuration](/content/search).

---

## Workstreams

Source: https://docs.vertesiahq.com/agent-runner/workstreams
Markdown: https://docs.vertesiahq.com/llms/agent-runner/workstreams.md

Workstreams are **non-blocking parallel sub-agents** that run independently alongside the parent agent. They enable agents to delegate research, analysis, computation, or authoring tasks to dedicated child agents that execute concurrently — dramatically speeding up complex multi-part work.

Workstreams replace the legacy `execute_parallel_work_streams` tool with a more flexible, non-blocking architecture. The parent agent continues working while children execute in the background.

## How It Works

```
Parent Agent
    |
    |-- launch_workstream("research_market")  --> Child Agent 1 (running)
    |-- launch_workstream("analyze_data")     --> Child Agent 2 (running)
    |-- launch_workstream("draft_report")     --> Child Agent 3 (running)
    |
    |   (parent continues working or waits)
    |
    |<-- workstream_completed signal <-- Child Agent 1 (done)
    |<-- workstream_completed signal <-- Child Agent 2 (done)
    |<-- workstream_completed signal <-- Child Agent 3 (done)
    |
    v
  Synthesize results
```

1. The parent calls `launch_workstream` — it returns **immediately** with a `launch_id`.
2. A child workflow starts in the background as a separate Temporal workflow.
3. The child communicates progress back to the parent via Temporal signals.
4. When the child completes, the parent receives a system message with the result summary.
5. Artifacts created by the child are automatically merged into the parent's workspace.

## When to Use Workstreams

**Use workstreams when:**
- The task can be decomposed into **independent subtasks** (e.g., research + analysis + drafting)
- You need **multiple perspectives** on the same topic (Expert Roundtable pattern)
- A subtask would be a **digression** from the main conversation flow
- You want to **parallelize** work that would otherwise be sequential

**Use sequential execution when:**
- Tasks have **strict dependencies** (output of A is input to B)
- The task is simple enough to complete **inline** in a single step
- You need **tight control** over every step of the process

## Workstream Lifecycle

Each workstream progresses through these states:

```
launched --> running --> completed
                    |-> failed
                    |-> timeout
                    |-> canceled (via terminate_workstream)
```

| State | Description |
|-------|-------------|
| `running` | Sub-agent is actively executing tools and reasoning |
| `canceling` | Termination requested; awaiting graceful shutdown (60-second grace period) |
| `completed` | Finished successfully — summary and artifacts available |
| `failed` | Encountered an unrecoverable error |
| `timeout` | Exceeded its deadline and was automatically terminated |
| `canceled` | Terminated by the parent via `terminate_workstream` |

## Progress Tracking

Workstreams report rich progress back to the parent, including:

- **Phase** — what the sub-agent is doing right now:
  - `planning` — analyzing the task and forming a plan
  - `executing_tool` — running a tool (tool name included)
  - `synthesizing` — combining results into a response
  - `blocked` — waiting for input or a dependency
  - `done` — finished processing
- **Current iteration** number (0-based)
- **Model message** — what the sub-agent is thinking about
- **Deadline percentage** — how much time has elapsed

Use `check_workstream` to query progress on demand, or `list_workstreams` for an overview of all workstreams.

## Deadline Management

Every workstream has a deadline to prevent runaway execution:

| Setting | Value |
|---------|-------|
| Default deadline | 5 minutes |
| Maximum deadline | 30 minutes |
| Minimum deadline | 30 seconds |

Configure the deadline via the `deadline_seconds` parameter on `launch_workstream`.

### Auto Wrap-Up

The system automatically manages deadlines:

1. At **75%** of the deadline, the parent receives a warning.
2. At **80%**, the child receives an automatic wrap-up steering directive telling it to finalize.
3. At **90%**, the parent receives a final warning.
4. At **100%**, the workstream is terminated and the parent receives a timeout notification with the last known progress.

## Interactive Workstreams

By default, workstreams run in **fire-and-forget** mode: the child executes its instruction and completes. For multi-turn conversations with a sub-agent, launch with `interactive: true`.

Interactive workstreams:
- Complete their initial task, then **wait** for follow-up messages
- Receive messages via `message_workstream`
- Support multi-round exchanges (e.g., asking follow-up questions, requesting refinements)
- **Never complete on their own** — the parent must call `terminate_workstream` to close the session

Interactive mode is the foundation of the [Expert Roundtable](#expert-roundtable-pattern) pattern.

## Artifact Merging

When `merge_child_artifacts` is `true` (the default), artifacts created by the child in its `out/` and `files/` directories are automatically copied back to the parent's artifact workspace after completion. Artifacts are **namespaced by child run ID** to avoid conflicts:

```
Parent workspace:
  files/{child_run_id}/output.json
  out/{child_run_id}/report.pdf
```

Set `merge_child_artifacts: false` to disable this behavior for workstreams that produce intermediate artifacts you don't need.

## Steering

The parent can send directives to a running workstream via `steer_workstream` without terminating it:

| Command | Use Case |
|---------|----------|
| `add_instruction` | "Also include pricing information in your analysis" |
| `set_priority` | "Focus on the security aspects first" |
| `add_constraint` | "Limit your analysis to the last 3 years" |
| `pause_requested` | Request the sub-agent to pause |
| `resume_requested` | Resume a paused sub-agent |

Steering directives are delivered as system messages that the child processes in its next reasoning turn.

## Best Practices

1. **Decompose clearly** — each workstream should have a single, well-defined objective. Vague instructions produce vague results.

2. **Provide rich context** — the sub-agent does **not** see the parent's conversation history. Include all relevant information in the `instruction` and `context` parameters.

3. **Scope tools carefully** — limit `allowed_tools` to what the task actually needs. A research task might only need `think` and `learn_web_search`, while a data analysis task might need `execute_shell` and artifact tools.

4. **Use meaningful IDs** — the `id` parameter appears in logs, progress messages, and the UI. Use descriptive names like `research_competitors` instead of `ws1`.

5. **Don't duplicate work** — avoid launching workstreams for tasks you can do faster inline. Workstreams have overhead (workflow startup, signal communication).

6. **Set appropriate deadlines** — use longer deadlines (up to 30 minutes) for complex tasks, shorter ones for simple research.

7. **Synthesize results** — when workstreams complete, review and combine their outputs into a coherent response rather than forwarding raw results.

8. **Don't poll** — results are delivered automatically via system messages. Simply end your turn and wait; the system handles notification.

## Expert Roundtable Pattern

The Expert Roundtable is an advanced multi-agent pattern where specialist sub-agents with distinct personalities and optionally different models debate a topic from multiple perspectives. This produces richer, less biased analysis than a single-agent approach.

### How It Works

1. **Decompose** the topic into 2-5 specialist perspectives (e.g., security architect, business strategist, technical lead).
2. **Launch** each specialist as an interactive workstream with a specific personality, model, and instructions.
3. **Collect** opening statements using `analyze_conversation` on the running child workflows.
4. **Cross-pollinate** — use `message_workstream` to pass opposing arguments between specialists for multi-round debate.
5. **Synthesize** — after convergence (or max 3 rounds), write a final synthesis that reconciles all perspectives.
6. **Terminate** all interactive workstreams when done.

```json
{
  "id": "security_expert",
  "name": "Security Architect",
  "instruction": "You are a skeptical security architect. Analyze this proposal from a security perspective...",
  "context": { "topic": "..." },
  "allowed_tools": ["think", "write_artifact"],
  "interactive": true,
  "model": "claude-sonnet-4-20250514",
  "deadline_seconds": 600
}
```

The Expert Roundtable pattern is available as the `learn_expert_roundtable` system skill, which provides detailed step-by-step instructions.

## Tools Reference

For detailed parameter documentation of all workstream tools, see [Built-in Tools — Workstream Tools](/agent-runner/tools#workstream-tools).

| Tool | Description |
|------|-------------|
| `launch_workstream` | Launch a non-blocking child sub-agent |
| `check_workstream` | Query status and progress |
| `list_workstreams` | List all workstreams with filtering |
| `terminate_workstream` | Cancel a running workstream |
| `steer_workstream` | Send mid-execution directives |
| `message_workstream` | Send messages to interactive workstreams |
| `get_workstream_result` | Retrieve completed workstream results |

---

## Introduction

Source: https://docs.vertesiahq.com/api/introduction
Markdown: https://docs.vertesiahq.com/llms/api/introduction.md

Welcome to the Vertesia Platform API documentation! This guide will walk you through the available endpoints and demonstrate how to use them with cURL and the client SDK.

## Platform API

The Vertesia Platform offers a comprehensive set of API endpoints, all available from a single URL:

* **Production URL:** [https://api.vertesia.io/api/v1](https://api.vertesia.io/api/v1)

## Authentication

Vertesia uses JWT tokens to authenticate API requests. The most convenient way to acquire a JWT token is to use the Vertesia CLI

## Code Examples

This documentation provides examples for each API call using:

* **cURL:** A command-line tool that can be used to make HTTP requests.
* **Vertesia Client SDK:** A convenient way to interact with the platform from your TypeScript or JavaScript code.

## API Documentation

[Go to the documentation](/api/openapi)

---

## Installed Application Settings

Source: https://docs.vertesiahq.com/apps/installation-settings
Markdown: https://docs.vertesiahq.com/llms/apps/installation-settings.md

Installed applications can be customized with their own settings.

To edit the settings for an installed application, select the "Edit Settings" button within the application card in Vertesia Studio (`Vertesia Studio > Settings > Applications > Installed Apps`).
- Note: If you do not have permission to edit the application, you will not be able to make changes.

## OAuth Credentials at Install Time

If an app's manifest declares MCP collections with [`oauth_config.required_at_install`](/apps/tool-collections#embedded-oauth-configuration), Vertesia displays a credential form before completing the installation. Required fields may include:

| Field | Description |
| ----- | ----------- |
| **Client ID** | Your OAuth client identifier, if each project registers its own client with the provider. |
| **Client Secret** | Your OAuth client secret. Secrets are encrypted at rest and never stored in the manifest. |
| **Scopes** | Additional OAuth scopes to request, added on top of the manifest's `default_scopes`. |

After install, the OAuth provider created from these credentials can be viewed and updated in **Project Settings > OAuth Providers**.
Install-time credentials are associated with each MCP collection by its stable `id`, not its display `name`.

## App Card Color

You can customize the colors used for the application card's appearance within the Vertesia App Portal. To do so, use the `color` property in the application settings:

```json
{
  "color": "<color_name>"
}
```

### Available Colors

When configuring application installations, you can choose from a variety of predefined colors to customize your applications's appearance:

  red
  orange
  amber
  yellow
  lime
  green
  emerald
  teal
  cyan
  sky
  blue
  indigo
  violet
  purple
  fuchsia
  pink
  rose

### Using Color Gradients

Applications support two different gradient formats for enhanced visual appearance:


### Using Gradient Stops

Gradients can be specified using color names with numeric gradient stops:

  - **Format:** `color-number`
  red-0
  red-1
  red-2
  red-3
  red-4


### Using Two Colors

Create gradients that blend between two different colors:

  - **Format:** `color-color`
    red-red
    red-orange
    red-amber
    red-yellow
    red-lime

---

## What are Applications?

Source: https://docs.vertesiahq.com/apps/overview
Markdown: https://docs.vertesiahq.com/llms/apps/overview.md

Applications are the way to customize the Vertesia platform for your projects.
By creating an application you can:

1. Contribute UI applications focused on the business model of your organization / project.
2. Contribute custom tools.
3. Contribute content types to your project.

Applications are installed at project level.

## Creating an application

Applications are created by registering the application manifest in `Vertesia Studio > Settings > Available Apps` page.

Each application must have an unique name in kebab case. We recommend prefixing the name of your application using your organization name: `vertesia-review-center`, `foo-contracts` etc.

## Application manifest

An application manifest is implementing the following interface:

```ts
interface AppManifestData {
    /**
     * The name of the app, used as the id in the system.
     * Must be in kebab case (e.g. my-app).
     */
    name: string;

    /**
     * Visibility of the app: "public", "private", or "vertesia".
     * Private apps are only visible to the owner organization.
     */
    visibility: "public" | "private" | "vertesia";

    title: string;
    description: string;
    publisher: string;

    /**
     * A svg icon for the app.
     */
    icon?: string;

    status: "beta" | "stable" | "deprecated";

    ui?: {
        /**
         * The source URL of the app. The src can be a template which contain
         * a variable named `buildId` which will be replaced with the current build id.
         * For example: `/plugins/vertesia-review-center-${buildId}`
         */
        src: string;

        /**
         * The isolation strategy. If not specified it defaults to shadow
         * - shadow - use Shadow DOM to fully isolate the plugin from the host.
         * - css - use CSS processing (like prefixing or other isolation techniques). Lighter but plugins may conflict with the host
         */
        isolation?: "shadow" | "css";
    },

    /**
     * MCP server configurations for external tool integrations.
     * See the Tool Collections page for full configuration options.
     */
    tool_collections?: ToolCollection[];

    /**
     * The app endpoint URL. Tools, interactions, UI, and settings
     * are discovered automatically from this URL.
     */
    endpoint?: string;
}
```

Applications can be private or public. Private applications can only be installed in projects of the same organization as the one who defined the application. The application will not be visible in other organizations.
Public applications are visible in all organizations.

In order to use an application you need to install it on a project.

## Installing an application on a project

Open the `Installations` tab in the applications settings page to see the list of the installed applications in the current project or to install a new application.

After installing the application you must define the users or groups that can access the application.
This can be done using the `Manage permissions` button.

## Contributing UI applications

To contribute a custom UI application you need to provide in the application manifest the `ui` property which specify the URL to the application javascript bundle as the `src` property and the type of isolation which is `shadow` by default (meaning that the application will be isolated in a shadow DOM).

Installed Applications that contribute an UI application are visible on the Vertesia Studio UI landing page for users who have the permission to access the application.

To learn how to develop an UI Application plugin go to [UI Plugin](/apps/ui) section.

## Contributing custom tools

Custom tools are contributed via the `endpoint` property — Vertesia automatically discovers tools from the endpoint URL. These servers can expose both regular tools and skills. Skills appear to agents as tools whose names start with the `learn_` prefix and cooperate with the Daytona sandbox and `execute_shell` for advanced code and data workflows. To learn how to write a tools server, see [Custom Tools](/agent-runner/custom-tools).

**Note** that only the users that can access the application can use these tools.

**Example:**

```json
{
  "name": "my-app",
  "endpoint": "https://my-tools-server.com/api/vertesia"
}
```

## Contributing MCP Servers

To connect external [MCP servers](/apps/tool-collections), use the `tool_collections` property. MCP servers support OAuth authentication. See [MCP Servers](/apps/tool-collections) for full configuration options.

**Example:**

```json
{
  "name": "my-app",
  "tool_collections": [
    {
      "type": "mcp",
      "url": "https://mcp.my-crm-provider.com",
      "name": "My CRM",
      "description": "CRM tools for contacts and deals",
      "namespace": "crm",
      "auth": "oauth",
      "oauth_app": "my-crm-oauth"
    }
  ]
}
```

## Contributing content types

Work is in progress for this feature.

---

## MCP Servers

Source: https://docs.vertesiahq.com/apps/tool-collections
Markdown: https://docs.vertesiahq.com/llms/apps/tool-collections.md

[MCP (Model Context Protocol)](https://modelcontextprotocol.io) servers connect external tools to your Vertesia applications. Agents can then use the tools provided by these servers during execution.

For custom HTTP tool servers built with the Vertesia Tools SDK, use the [`endpoint`](/apps/overview) field in your app manifest instead — tools are discovered automatically from the endpoint URL.

## Quick Start

Add an MCP server to your app manifest's `tool_collections` array:

```json
{
  "name": "my-app",
  "tool_collections": [
    {
      "type": "mcp",
      "id": "example_tools",
      "url": "https://mcp.example.com/v1",
      "name": "Example Tools",
      "description": "Tools from Example service",
      "namespace": "example",
      "auth": "oauth"
    }
  ]
}
```

## MCP Configuration Fields

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `type` | `string` | Yes | Must be `"mcp"`. |
| `url` | `string` | Yes | The MCP server URL. |
| `name` | `string` | Yes | Human-readable name for the tool collection. Must be unique within the manifest. |
| `description` | `string` | Yes | Human-readable description of the tools provided. |
| `namespace` | `string` | Yes | Prefix added to tool names to avoid collisions (e.g., `example_search`). |
| `id` | `string` | Yes | Stable identifier for this collection. Must be `snake_case`. Used for install-time bindings and should not change after publish. |
| `auth` | `string` | No | Authentication type. Set to `"oauth"` for OAuth-protected servers. |
| `oauth_app` | `string` | No | Name of a manually created [OAuth Provider](/api/oauth-providers). Use this legacy/manual path when you need full control over the provider lifecycle. |
| `oauth_config` | `object` | No | OAuth configuration embedded directly in the manifest. Vertesia auto-creates an OAuth Provider when the app is installed. See [Embedded OAuth Configuration](#embedded-oauth-configuration) below. Mutually exclusive with `oauth_provider`. |
| `oauth_provider` | `string` | No | Key of a shared provider declared in the top-level `oauth_providers` map. Mutually exclusive with `oauth_config` and `oauth_app`. Requires `auth: "oauth"`. See [Shared OAuth Providers](#shared-oauth-providers). |
| `oauth_scopes` | `string[]` | No | Additional OAuth scopes for this collection when using `oauth_provider`. Merged with the provider's `default_scopes` and any installer-supplied scopes at install time. |

The distinction between `id` and `name` matters:

- `id` is the stable machine identifier. Vertesia uses it for install-time OAuth bindings and related runtime lookups.
- `name` is the human-readable label shown in the UI.

### Namespacing

The `namespace` field is prepended to all tool names from the MCP server. For example, if the server exposes a tool named `search` and the namespace is `crm`, the agent sees it as `crm_search`. This prevents name collisions when multiple MCP servers are configured.

## OAuth Authentication

Some MCP servers require OAuth 2.0 authentication. Vertesia handles the full OAuth flow including token storage and automatic refresh.

There are four ways to configure OAuth for an MCP collection, in order of preference:

1. **Shared `oauth_providers` map** — Declare a named OAuth provider once at the manifest level. Multiple collections reference it by key. One `OAuthProvider` is created per provider at install time. Best when multiple MCP servers share the same OAuth client (e.g. Microsoft Graph and Microsoft Teams).
2. **Embedded `oauth_config`** — Declare OAuth parameters directly on the collection. Vertesia auto-creates an `OAuthProvider` when the app is installed. Best for distributing apps to multiple projects where each MCP server has its own OAuth client.
3. **`oauth_app` reference** — Point to a manually created OAuth Provider by name. Best for full lifecycle control or sharing a single provider across multiple collections.
4. **Remote server discovery** — If no provider is configured, Vertesia can discover OAuth metadata from the remote MCP server and follow that server's flow.

## Shared OAuth Providers

Use `oauth_providers` when multiple MCP servers in the same app share a single OAuth client — for example, Microsoft Graph API and Microsoft Teams both use the same Azure OAuth provider configuration.

Declare providers once at the manifest level, then reference them from each collection using `oauth_provider`:

```json
{
  "name": "my-microsoft-app",
  "oauth_providers": {
    "microsoft": {
      "grant_type": "authorization_code",
      "client_id": "your-azure-app-client-id",
      "use_pkce": true,
      "default_scopes": ["offline_access"],
      "required_at_install": ["client_secret"]
    }
  },
  "tool_collections": [
    {
      "type": "mcp",
      "id": "ms_graph",
      "url": "https://graph.microsoft.com/mcp",
      "name": "Microsoft Graph",
      "description": "Access Microsoft 365 data",
      "namespace": "graph",
      "auth": "oauth",
      "oauth_provider": "microsoft",
      "oauth_scopes": ["User.Read", "Mail.Read"]
    },
    {
      "type": "mcp",
      "id": "ms_teams",
      "url": "https://teams.microsoft.com/mcp",
      "name": "Microsoft Teams",
      "description": "Send messages and manage channels",
      "namespace": "teams",
      "auth": "oauth",
      "oauth_provider": "microsoft",
      "oauth_scopes": ["ChannelMessage.Send"]
    }
  ]
}
```

At install time, Vertesia:

1. Creates **one** `OAuthProvider` named `my-microsoft-app-microsoft`.
2. Computes the final scope set: `offline_access` (provider default) ∪ `User.Read Mail.Read` (graph) ∪ `ChannelMessage.Send` (teams).
3. Stores a `provider_bindings` entry on the installation linking the `microsoft` key to the created OAuth Provider.

Both collections then share the same token at runtime.

### `oauth_providers` Fields

Each entry in the `oauth_providers` map uses the same fields as `oauth_config`:

| Field | Type | Description |
| ----- | ---- | ----------- |
| `grant_type` | `string` | OAuth grant type: `"authorization_code"` (user OAuth flow) or `"client_credentials"` (machine-to-machine, no user consent). |
| `authorization_endpoint` | `string` | OAuth authorize URL. Optional — auto-discovered from `.well-known/oauth-authorization-server` when omitted. |
| `token_endpoint` | `string` | OAuth token exchange URL. Optional — auto-discovered when omitted. |
| `revocation_endpoint` | `string` | Token revocation URL. Optional. |
| `client_id` | `string` | Pre-configured client ID shared across all installs. Omit if each project registers its own OAuth client. |
| `use_pkce` | `boolean` | Enable PKCE (Proof Key for Code Exchange). Defaults to `true`. |
| `default_scopes` | `string[]` | Base scopes applied to **all** collections referencing this provider. |
| `required_at_install` | `string[]` | Installer-supplied credentials. See [`required_at_install`](#required_at_install) below. |
| `name` | `string` | Name for the created `OAuthProvider`. Defaults to `{app-slug}-{providerKey}` (e.g. `my-microsoft-app-microsoft`). |
| `display_name` | `string` | Display name for the created `OAuthProvider`. |

### Scope Union

The final scope set on the created `OAuthProvider` is:

```text
provider.default_scopes ∪ collection₁.oauth_scopes ∪ collection₂.oauth_scopes ∪ installer_scopes
```

Duplicates are removed. The unified scope list is stored on the `OAuthProvider` and used for every collection that references this provider.

### Scope Changes After Install

Changing `oauth_providers[key].default_scopes` or a collection's `oauth_scopes` in the manifest **does not automatically update already-installed `OAuthProvider` documents**. To apply updated scopes, the installer must uninstall and reinstall the app — this deletes the old provider and creates a fresh one from the updated manifest.

### Mutual Exclusivity

A collection may use at most one OAuth configuration method. These combinations are rejected at manifest save time:

| Invalid combination | Error |
| ------------------- | ----- |
| `oauth_provider` + `oauth_config` | Mutually exclusive |
| `oauth_provider` + `oauth_app` | Mutually exclusive |
| `oauth_scopes` without `oauth_provider` | Not meaningful outside a shared provider |
| `oauth_provider` without `auth: "oauth"` | `auth: "oauth"` is required |
| `oauth_provider` referencing a key not in `oauth_providers` | Unknown provider |

## Embedded OAuth Configuration

Use `oauth_config` to embed OAuth parameters directly in the manifest. When a user installs the app, Vertesia automatically creates an `OAuthProvider` in their project using the configuration you provide, optionally merged with credentials the installer supplies at install time.

This is the recommended approach for apps distributed to multiple projects — each project gets its own OAuth Provider with project-scoped credentials.

`oauth_config` is install-time configuration only. At runtime, Vertesia resolves OAuth through the installation's stored binding from `collection.id` to the created OAuth Provider.

### `oauth_config` Fields

| Field | Type | Description |
| ----- | ---- | ----------- |
| `grant_type` | `string` | OAuth grant type: `"authorization_code"` (user OAuth flow) or `"client_credentials"` (machine-to-machine, no user consent). |
| `authorization_endpoint` | `string` | OAuth authorize URL. Optional — auto-discovered from `.well-known/oauth-authorization-server` when omitted. |
| `token_endpoint` | `string` | OAuth token exchange URL. Optional — auto-discovered when omitted. |
| `revocation_endpoint` | `string` | Token revocation URL. Optional. |
| `client_id` | `string` | Pre-configured client ID shared across all installs. Omit if each project registers its own OAuth client. |
| `use_pkce` | `boolean` | Enable PKCE. Defaults to `true`. |
| `default_scopes` | `string[]` | Scopes to request by default. |
| `required_at_install` | `string[]` | Parameters installers must provide at install time. Accepted values: `"client_id"`, `"client_secret"`, `"scopes"`. See below. |
| `name` | `string` | Name for the created `OAuthProvider`. Defaults to the collection `id` converted to kebab-case. |
| `display_name` | `string` | Display name for the created `OAuthProvider`. |

### `required_at_install`

Use `required_at_install` to prompt the person installing the app for credentials that cannot be stored in a shared manifest:

| Value | When to use |
| ----- | ----------- |
| `"client_id"` | Each project registers its own OAuth client with the provider (e.g., users bring their own app). |
| `"client_secret"` | The OAuth provider requires a client secret — secrets must never be embedded in a shared manifest. |
| `"scopes"` | Allow installers to specify additional scopes beyond the `default_scopes`. |

When `required_at_install` is set, Vertesia displays a credential form during the install flow before creating the OAuth Provider.

`client_secret` must never be stored in the manifest. If a secret is required, declare `"client_secret"` in `required_at_install` so the installer provides it at install time.

### Example: Shared Client ID, Secret Required at Install

```json
{
  "tool_collections": [
    {
      "type": "mcp",
      "id": "my-crm",
      "url": "https://mcp.my-crm-provider.com",
      "name": "My CRM",
      "description": "CRM tools for contacts and deals",
      "namespace": "crm",
      "auth": "oauth",
      "oauth_config": {
        "grant_type": "authorization_code",
        "client_id": "shared-client-id-from-provider",
        "use_pkce": true,
        "default_scopes": ["read", "write"],
        "required_at_install": ["client_secret"]
      }
    }
  ]
}
```

### Example: Each Installer Brings Their Own OAuth Client

```json
{
  "tool_collections": [
    {
      "type": "mcp",
      "id": "my-crm",
      "url": "https://mcp.my-crm-provider.com",
      "name": "My CRM",
      "description": "CRM tools for contacts and deals",
      "namespace": "crm",
      "auth": "oauth",
      "oauth_config": {
        "grant_type": "authorization_code",
        "authorization_endpoint": "https://auth.my-crm-provider.com/oauth/authorize",
        "token_endpoint": "https://auth.my-crm-provider.com/oauth/token",
        "use_pkce": true,
        "required_at_install": ["client_id", "client_secret"]
      }
    }
  ]
}
```

### How Auto-Provisioning Works

At install time, Vertesia:

1. Merges `oauth_config` with the credentials the installer provided.
2. Creates an `OAuthProvider` in the installing project.
3. Stores a binding (`collection id` → `OAuth Provider id`) on the installation record.

Credentials can be updated later from **Project Settings > OAuth Providers**. If an OAuth provider with the same generated name already exists, install will fail until the name conflict is resolved or `oauth_config.name` is changed.

## Manual OAuth Provider

For full control over the OAuth provider lifecycle, create one manually and reference it by name:

### Step 1: Create an OAuth Provider

Go to **Project Settings > OAuth Providers** and click **Create**. Fill in:

| Field | Description |
|-------|-------------|
| **Name** | Kebab-case identifier (e.g., `my-crm-oauth`). Used to reference the app in manifests. |
| **Display Name** | Human-readable label shown in the UI. |
| **Client ID** | OAuth client identifier from the provider. |
| **Client Secret** | OAuth client secret (encrypted at rest). Optional for public clients using PKCE. |
| **Default Scopes** | Space-separated OAuth scopes (e.g., `read write`). |
| **Use PKCE** | Enable PKCE (Proof Key for Code Exchange). Enabled by default. |
| **Authorization Endpoint** | OAuth authorize URL. Optional — auto-discovered from the MCP server's `.well-known/oauth-authorization-server` metadata when omitted. |
| **Token Endpoint** | OAuth token exchange URL. Optional — auto-discovered when omitted. |
| **Revocation Endpoint** | Token revocation URL. Optional. |

For MCP servers that publish OAuth metadata at `.well-known/oauth-authorization-server`, you only need to provide the **Client ID** (and **Client Secret** if required by the provider). The authorization and token endpoints are discovered automatically.

See the [OAuth Providers API reference](/api/oauth-providers) for programmatic management.

### Step 2: Reference the OAuth Provider in the Manifest

Add the `auth` and `oauth_app` fields to the MCP collection:

```json
{
  "tool_collections": [
    {
      "type": "mcp",
      "id": "my_crm",
      "url": "https://mcp.my-crm-provider.com",
      "name": "My CRM",
      "description": "CRM tools for contacts and deals",
      "namespace": "crm",
      "auth": "oauth",
      "oauth_app": "my-crm-oauth"
    }
  ]
}
```

The `oauth_app` value must match the **Name** of the OAuth Provider you created in Step 1.

### Step 3: Connect

Each user must authenticate individually before agents can use the MCP tools on their behalf.

**Admins** can verify the configuration works from **Project Settings > Apps**: find the installed app and click **Connect** next to the OAuth-enabled MCP collection to complete the OAuth consent flow.

**End users** connect from the **new agent form** in the Agent Runner interface before starting the agent. The form displays connect actions for any OAuth-enabled MCP collections that require authentication.

Tokens are stored securely and refreshed automatically. If a refresh token expires or is revoked, the user will need to reconnect.

## Complete Example

An app manifest combining embedded OAuth configuration and a public MCP server:

```json
{
  "name": "my-sales-app",
  "visibility": "private",
  "title": "Sales Integration",
  "description": "Connect sales tools to Vertesia agents",
  "publisher": "My Organization",
  "status": "stable",
  "endpoint": "https://my-app.example.com/api/vertesia",
  "tool_collections": [
    {
      "type": "mcp",
      "id": "crm",
      "url": "https://mcp.my-crm-provider.com",
      "name": "My CRM",
      "description": "CRM tools for contacts, deals, and companies",
      "namespace": "crm",
      "auth": "oauth",
      "oauth_config": {
        "grant_type": "authorization_code",
        "client_id": "shared-client-id",
        "use_pkce": true,
        "default_scopes": ["crm.read", "crm.write"],
        "required_at_install": ["client_secret"]
      }
    },
    {
      "type": "mcp",
      "id": "internal_tools",
      "url": "https://mcp.internal.example.com/v1",
      "name": "Internal Tools",
      "description": "Internal MCP tools",
      "namespace": "internal"
    }
  ]
}
```

This example configures:
- An `endpoint` for Vertesia SDK tools (discovered automatically)
- An MCP server with embedded OAuth configuration — the installer is prompted for a client secret at install time, then an `OAuthProvider` is auto-created in their project
- An unauthenticated MCP server

---

## Building a UI Plugin

Source: https://docs.vertesiahq.com/apps/ui
Markdown: https://docs.vertesiahq.com/llms/apps/ui.md

UI plugins extend Vertesia Studio with custom React pages. Each plugin is a unified project containing a **React UI** (frontend) and a **Hono tool server** (backend for custom tools, skills, and interactions), built and deployed as a single unit.

## Prerequisites

- Node.js 22+ and npm or pnpm
- [Vertesia CLI](https://www.npmjs.com/package/@vertesia/cli) installed and authenticated (`vertesia auth login`)

## 1. Scaffold Your Plugin

```bash
npm init @vertesia/plugin@latest
```

You will be prompted for the plugin name (kebab-case), version, description, and isolation strategy (`shadow` recommended). The generated project includes everything needed: React UI, Hono tool server, Vite + Rollup build configs, Vercel deployment config, and example tools/skills.

## 2. Develop Locally

```bash
cd my-plugin
npm install
npm run dev
```

The generated `dev` script runs Vite in app mode (`vite dev --mode app`), so local development reads `.env.app` and `.env.app.local`.

Open `https://localhost:5173`. You'll see:

- **`/app/*`** -- your plugin UI with hot module replacement
- **`/*`** -- the tool server admin UI (manage tools, skills, interactions)
- **`/api`** -- the tool server API endpoint

HTTPS is required for authentication. The dev server uses a self-signed certificate.

The generated project includes an `.env.app` file:

```bash
VITE_APP_NAME=my-plugin
```

This value must match the `name` field in your Vertesia app manifest. It is public Vite build-time configuration, so it is safe to commit. Use `.env.app.local` for local overrides. Generic Vite development env files such as `.env.local` are not required by the generated template.

## 3. Register Your App in Vertesia

Create a `manifest.json`:

```json
{
  "name": "my-plugin",
  "title": "My Plugin",
  "description": "What this plugin does",
  "publisher": "your-org",
  "visibility": "private",
  "status": "beta"
}
```

Register and install in one step:

```bash
vertesia apps create --install -f manifest.json
```

This creates the app manifest, installs it in your current project, and grants you access.

Verify that `.env.app` uses the same app name:

```bash
VITE_APP_NAME=my-plugin
```

Restart `npm run dev` and navigate to `/app/` to see your plugin running with full Vertesia authentication.

## 4. Deploy to Vercel

[Vercel](https://vercel.com) is the easiest way to deploy your plugin — its generous free tier is more than enough for development and small-scale production.

```bash
npm i -g vercel
vercel --prod
```

The template includes a `vercel.json` and `api/index.js` adapter that handles routing: the standalone app is served at `/app`, `/` redirects to `/app`, and API requests go through the serverless function.

Vercel builds with `vite build --mode app`, so it reads `.env.app` and `.env.app.local` if present. Do not rely on `.env.local` for deployed builds. If you override `VITE_APP_NAME` in Vercel project settings, keep it equal to the manifest `name`.

After deploying, update your app manifest with the production endpoint:

```bash
vertesia apps update my-plugin --manifest '{
  "endpoint": "https://my-plugin.vercel.app/api"
}'
```

The `endpoint` URL tells Vertesia where to find your plugin's tools, skills, interactions, and UI configuration. It replaces the older `ui.src` and `tool_collections` fields.

**Important**: Disable deployment protection in Vercel project settings for the plugin to be publicly accessible.

### Isolation Strategies

The manifest `ui.isolation` field controls how the plugin CSS interacts with the host app:

- **`shadow`** (default, recommended) -- Shadow DOM fully isolates plugin styles
- **`css`** -- lighter weight, but plugin styles may conflict with the host. Required if using Radix UI portals (modals with inputs)

## Using Vertesia UI Components

  For a complete catalog of available components with live examples, props
  tables, and usage snippets, see the [Components reference](/components/buttons).

The `@vertesia/ui` package provides ready-to-use components. Import from subpaths:

```tsx
// Core components and hooks
import { Button, Card, Input, Spinner, VModal, VTabs, useFetch, useToast } from '@vertesia/ui/core';

// Router
import { useNavigate, useParams, NavLink, NestedRouterProvider } from '@vertesia/ui/router';

// Session and auth
import { useUserSession } from '@vertesia/ui/session';

// Layout
import { FullHeightLayout } from '@vertesia/ui/layout';
```

### Fetching Data

Use the `useFetch` hook with the Vertesia client:

```tsx
import { useFetch, Spinner } from '@vertesia/ui/core';
import { useUserSession } from '@vertesia/ui/session';

function MyPage() {
    const { client } = useUserSession();

    const { data, error } = useFetch(
        () => client.store.collections.list(),
        []
    );

    if (error) return <div>Failed to load</div>;
    if (!data) return <Spinner />;

    return <div>{data.map(item => ...)}</div>;
}
```

### Using the Vertesia Client

```tsx
const { client } = useUserSession();

// Collections and objects
const collections = await client.store.collections.list();
await client.store.objects.create(
    { content: file, name: file.name },
    { collection_id: collectionId }
);

// Launch an agent
await client.runs.create({
    interaction: 'app:my_interaction',
    data: { /* payload */ },
    tags: ['my-tag'],
});
```

### Styling

  For a complete catalog of available styles with live examples, see the [Styling reference](/components/semantic).

Use Tailwind CSS with Vertesia's semantic color classes:

```tsx
<div className="text-success bg-success border-success" />
<div className="text-destructive bg-destructive" />
<div className="text-muted" />
```

For these classes to be generated by Tailwind, your plugin must pull the Vertesia design tokens into its main Tailwind entry CSS so the `@theme` directives are visible at compile time:

```css {{ title: 'src/styles/tailwind.css' }}
@import 'tailwindcss';

/*
 * Pull in the Vertesia design tokens so the Tailwind compilation knows
 * about the semantic palette (--color-primary, --color-foreground, etc.).
 * Without this, classes like `bg-primary` and `text-muted` never get
 * generated as utilities.
 *
 * Skip @vertesia/ui/css/base.css if you have your own base layer — it
 * body-applies selection styles that may conflict with your palette.
 */
@import '@vertesia/ui/css/color.css';
@import '@vertesia/ui/css/theme.css';
@import '@vertesia/ui/css/utilities.css';
@import '@vertesia/ui/css/custom-tooltips.css';
```

If the import lives inside a component file (e.g. `import '@vertesia/ui/css/index.css'` from a TSX module) instead of the Tailwind entry CSS, the CSS variables are bundled but Tailwind never sees the `@theme` block at compile time, so the semantic utility classes are silently missing.

## Next Steps

The generated project includes a comprehensive README with detailed guides for:

- **Creating resources** -- tools, skills, interactions, content types, templates
- **Tool server configuration** -- registering collections, settings schema, org restrictions
- **Build system** -- dual Rollup/Vite architecture, import hooks
- **Debugging with the platform** -- Cloudflare tunnel for local testing with real agents
- **Theme customization** -- overriding CSS custom properties in `index.css`

---

## Vertesia CLI

Source: https://docs.vertesiahq.com/cli
Markdown: https://docs.vertesiahq.com/llms/cli.md

The Vertesia CLI provides a set of commands to manage and interact with the Vertesia Platform. This documentation covers all available commands, grouped by their logical functionality, and provides examples on how to use each command.

## Installation

To install the CLI, follow these steps:

1. **Ensure Node.js is installed**: The Vertesia CLI requires Node.js. You can download and install it from [nodejs.org](https://nodejs.org/). You can verify Node.js is installed by running:

    ```bash
    node --version
    ```

2. **Install the Vertesia CLI globally**: Open your terminal and run the following command to install the Vertesia CLI globally using npm:

    ```bash
    npm install -g @vertesia/cli
    ```

3. **Verify the installation**: After installation, you can verify that the Vertesia CLI is installed correctly by running:

    ```bash
    vertesia --version
    ```

This command displays the version of the CLI installed on your system.

## Help

To get help with the CLI.

  ```bash
  vertesia --help
  ```

To get help with any CLI command.
   ```bash
  vertesia help [command]
  ```

Option to get help with any CLI command.
   ```bash
  vertesia <command> --help
  ```

## Authentication

Commands to manage authentication.

### Commands

- `auth token`: Get a JWT token for the API key used in the authentication.

  ```bash
  vertesia auth token
  ```

- `auth refresh`: Refresh the JWT token.

  ```bash
  vertesia auth pk <projectId> --name <keyName> --ttl <timeToLive>
  ```

## Profiles

Commands to manage configuration profiles which enable using the CLI with different Vertesia accounts, projects, and infrastructure.

### Commands

- `profiles show [name]`: Show the configured profiles or the profile with the given name.

  ```bash
  vertesia profiles show [name]
  ```

- `profiles use [name]`: Switch to another configuration profile.

  ```bash
  vertesia profiles use [name]
  ```

- `profiles add [name] [options]`: Create a new configuration profile.

  ```bash
  vertesia profiles add [name] --target <environment>
  ```

- `profiles edit [name]`: Edit an existing configuration profile.

  ```bash
  vertesia profiles edit [name]
  ```

- `profiles refresh`: Refresh token for the current configuration profile.

  ```bash
  vertesia profiles refresh
  ```

- `profiles delete `: Delete an existing configuration profile.

  ```bash
  vertesia profiles delete <name>
  ```

- `profiles file`: Print the configuration file path.

  ```bash
  vertesia profiles file
  ```

## Projects

Command to list projects.

### Command

- `projects`: List the projects you have access to.

  ```bash
  vertesia projects
  ```

## Environments

Command to list environments.

### Command

- `envs [envId]`: List the environments you have access to.

  ```bash
  vertesia envs [envId]
  ```

## Interactions

Commands to list interactions, generate test data, run, and search interactions.

### Commands

- `interactions`: List the interactions available in the current project.

  ```bash
  vertesia interactions
  ```

- `interactions `: List the details of an interaction given its ID.

  ```bash
  vertesia interactions <interactionId>
  ```

- `datagen  [options]`: Generate test  data for an interaction given its ID.

  ```bash
  vertesia datagen <interactionId> --env <envId> --model <model> --temperature <value> --output <file> --count <number>
  ```

- `run  [options]`: Run an interaction by ID.

  ```bash
  vertesia run <interactionId> --input <file> --output <file> --data <json> --tags <tags> --temperature <temperature> --model <model> --env <environmentId> --no-stream --count <count> --verbose --jsonl --data-only
  ```

- `runs  [options]`: Search the run history by interaction ID.

  ```bash
  vertesia runs <interactionId> --tags <tags> --status <status> --env <environmentId> --model <model> --query <query> --limit <limit> --page <page> --format <format> --output <file> --before <date> --after <date>
  ```

## Content Objects

Commands to manage content objects.

### Commands

- `content post <file...> [options]`: Post a new object to the store. The path to the file can include wildcards by using `*`.

  ```bash
  vertesia content post <file...> --name <name> --type <type> --mime <mime> --path <parentPath> --recursive
  ```

- `content delete `: Delete an existing object given its ID.

  ```bash
  vertesia content delete <objectId>
  ```

- `content get `: Get an existing object given its ID.

  ```bash
  vertesia content get <objectId>
  ```

- `content list  [options]`: List the objects inside a folder.

  ```bash
  vertesia content list <folderPath> --limit <limit> --skip <skip>
  ```

## Workflow

Commands to manage workflows and workflow rules.

### Commands

- `workflows rules create [options]`: Create a new workflow rule.

  ```bash
  vertesia workflows rules create --name <name> --on <event> --run <endpoint>
  ```

- `workflows rules get  [options]`: Get a workflow rule given its ID.

  ```bash
  vertesia workflows rules get <workflowId> --file <file>
  ```

- `workflows rules apply [options]`: Apply a workflow rule.

  ```bash
  vertesia workflows rules apply --file <file>
  ```

- `workflows rules list`: List all workflow rules.

  ```bash
  vertesia workflows rules list
  ```

- `workflows rules execute  [options]`: Execute a workflow rule given its ID.

  ```bash
  vertesia workflows rules execute <workflowId> --objectId <objectId>
  ```

- `workflows rules delete `: Delete a workflow rule given its ID.

  ```bash
  vertesia workflows rules delete <objectId>
  ```

- `workflows definitions transpile <files...> [options]`: Transpile a TypeScript workflow definition to JSON.

  ```bash
  vertesia workflows definitions transpile <files...> --out <file>
  ```

- `workflows definitions create [options]`: Create a new workflow definition.

  ```bash
  vertesia workflows definitions create --file <file>
  ```

- `workflows definitions apply [workflowId] [options]`: Apply a workflow definition.

  ```bash
  vertesia workflows definitions apply [workflowId] --file <file> --skip-validation
  ```

- `workflows definitions list`: List all workflow definitions.

  ```bash
  vertesia workflows definitions list
  ```

- `workflows definitions get  [options]`: Get a workflow definition given its ID.

  ```bash
  vertesia workflows definitions get <objectId> --file <file>
  ```

- `workflows definitions delete `: Delete a workflow definition given its ID.

  ```bash
  vertesia workflows definitions delete <objectId>
  ```

## Code Generation

Commands to generate code based on interactions.

### Commands

- `codegen [interactionName]`: Generate code given an interaction name or for all the interactions in the project.

  ```bash
  vertesia codegen [interactionName] --versions <versions> --all --dir <file> --export <version>
  ```

## Package Management

Commands to manage the Vertesia CLI package.

This command checks if there is a newer version of the CLI available. If a new version is found, it prompts the user to confirm the upgrade. If the user confirms, the Vertesia CLI will update itself to the latest version. If no updates are available, it will notify the user.

### Commands

- `upgrade`: Upgrade to the latest version of the CLI.

  ```bash
  vertesia upgrade
  ```

---

## Concepts

Source: https://docs.vertesiahq.com/concepts
Markdown: https://docs.vertesiahq.com/llms/concepts.md

Here are the core concepts for working with the Vertesia Platform.

## Large Language Models (LLM)

Generative AI is based on the capability of interpreting human languages - and reply to questions using a human language.

There are key points to know about them:
- Large Language Models (LLMs) are capable of interpreting human languages (including programming languages for instance).
- Human language is found in unstructured (image, video, audio) and structured (document,database record) contents.
- Models are pre-trained on very large sets of contents (public, private, hybrid).
- Models can be fine tuned to further adapt them, a posteriori, to specific knowledge (e.g. Enterprise contents such as suppliers contracts, corporate policies) - but that’s not necessarily a good approach.
- Retrieval Augmented Generation (RAG) is a good alternative to fine tuning for adding knowledge to models.
- Though chatBots have been the first well known application of generative AI, they fall far behind the full potential of Gen AI.
- LLMs can be seen as processing entities that can be requested in an automated way - and enable a very wide range of use cases.

## A bit of jargon

| Term | Description |
| --- | --- |
| Context Window | Imagine you have a really big notebook that you use to take notes while you are watching a movie. The notebook can only hold so many pages at a time, and once it is full, you can only look at what is written on those pages. In the world of large language models (LLMs), the notebook is called the *context window*. It is the amount of text (or *tokens*) that the model can “remember” or consider at one time when it is answering a question or having a conversation. Each model has a distinct context window. |
| Embeddings | Embeddings are high-dimensional vectors that represent tokens (words) in a way that captures their semantic meaning and relationships. These vectors are learned during the training of the LLM and are crucial for the model's ability to understand and generate language. |
| Max tokens | Maximum number of tokens (words/characters) for the model to generate in the output. In some models, it is taken on the context window length. |
| Similarity | If two tokens (words) are very *similar* in meaning, like "happy" and "joyful," their numerical representation will be next to each other. If the words are very different, like "happy" and "fast," their numbers will be farther apart. This numerical way of arranging words is what we call an embedding. |
| Token | A unit of text that the model processes. Tokens can be words, subwords, characters, or even punctuation marks. The process of breaking down text into these smaller units is known as tokenization. This allows the model to handle and generate text more efficiently by working with manageable pieces of information. |


## Prompt Templates

`Prompt Templates` are the building blocks of prompts and are used to create prompts.
Prompts are then assembled to define a prompt for a task (Interaction).

Each prompt template has a `content_type` that determines how variables are injected into the prompt content.

        Uses [Handlebars](https://handlebarsjs.com/) syntax with double curly braces for variable substitution.
        Supports `{{variable}}` references, conditionals (`{{#if}}`/`{{else}}`/`{{/if}}`),
        loops (`{{#each items}}`), and built-in helpers like `{{_now}}` (current timestamp)
        and `{{stringify obj}}` (JSON serialization).
        Ideal for most use cases thanks to its simple, readable syntax.
        Set `content_type` to `"handlebars"`.
        For advanced composition and templating that goes beyond Handlebars.
        A JavaScript template engine running in a sandboxed environment.
        Uses standard JavaScript string interpolation syntax (`${var}`),
        as well as control blocks (`for`, `if`, `else`, etc.),
        and array functions (`map`, `reduce`, `filter`, etc.).
        Provides a utility object `_` with helpers: `_.loadCsv()`, `_.jsonToCsv()`,
        `_.stringify()`, `_.addLineNumbers()`, and `_.dayjs()` for date manipulation.
        Use JST when you need complex data transformations, CSV processing,
        or programmatic prompt construction.
        The template must return a string.
        Set `content_type` to `"jst"`.

## Interactions

`Interactions` define the tasks the LLM are requested to perform.

An interaction is defined by the following main components:

    The name of the interaction.
    A description of the interaction.
        A list of prompts templates to be rendered as part of the final prompt.
        JSON Schema requested from the generative model for the response. It will be
        used to validate the response as well.
        [Environment](#environments) and Model to execute the interaction on, and
        execution parameters.

## Runs

Runs are the execution of an interaction, it is both the request to and the response from the generative model.

Runs have the following statuses:

        The run has been created, but not yet started. Typically the case when
        waiting for the streaming start from the client.
    The run is currently executing.
    The run has completed successfully.
        The run has failed. The failure reason is in the field `error`.

## Environments

Environments connect to LLM inference providers which are the execution platforms running generative models.

We currently support environments for the following inference providers:
- `azure_openai` - [Azure OpenAI Service](https://azure.microsoft.com/en-us/products/ai-services/openai-service)
- `bedrock` - [Amazon Bedrock](https://aws.amazon.com/bedrock/)
- `groq` - [Groq](https://groq.com)
- `huggingface_ie` - [Hugging Face's Inference Endpoint](https://huggingface.co/inference-endpoints/dedicated)
- `mistralai` - [Mistral AI's La Platforme](https://docs.mistral.ai/deployment/laplateforme/overview/)
- `openai` - [OpenAI](https://platform.openai.com)
- `replicate` - [Replicate](https://replicate.com)
- `togetherai` - [TogetherAI](https://www.together.ai)
- `vertexai` - [Google's Vertex AI](https://cloud.google.com/vertex-ai)
- `watsonx` - [IBM's watsonx.ai](https://www.ibm.com/products/watsonx-ai)

In addition to the core inference providers above, we have created virtual providers to assemble models and platform into a virtual, synthetic LLM, and offer several balancing and execution strategies:
-  `virtual_lb` - a synthetic environment that allows load balancing and failover between multiple models
-  `virtual_mediator` - a synthetic environment that allows multi-head execution and LLM mediation

## Data Platform

The Data Platform provides unified data management capabilities for structured data, analytics, and visualization.

### DataStores

DataStores are DuckDB databases that store structured data with the following features:

        Schema-defined tables with typed columns (STRING, INTEGER, DECIMAL, BOOLEAN, DATE, TIMESTAMP, JSON).
        Automatic version snapshots on schema changes and data imports, with rollback capability.
        Full SQL support with DuckDB extensions including window functions, CTEs, and QUALIFY clause.

### Dashboards

Dashboards are Vega-Lite visualizations backed by SQL queries:

        Industry-standard visualization grammar supporting bar, line, area, pie, scatter, and heatmap charts.
        Selections in one panel can filter data in other panels for interactive exploration.
        Dynamic SQL with `{{param}}` placeholders for reusable, parameterized dashboards.

### Relationship with Collections

DataStores can be linked to Collections through tag-based associations:

- Collections hold source files (CSV, JSON, Parquet, Excel)
- DataStores hold the structured, queryable data
- Projects link them together via `dp:<project-slug>` tags
- AI agents can analyze files and automatically create database schemas

For detailed documentation, see the [Data Platform Overview](/data-platform/overview).

---

## Embeddings Configuration

Source: https://docs.vertesiahq.com/content/embeddings
Markdown: https://docs.vertesiahq.com/llms/content/embeddings.md

Embeddings are numerical representations of content that capture semantic meaning, enabling powerful similarity search and content understanding. Vertesia supports multiple embedding types to index different aspects of your documents.

## Understanding Embeddings

An embedding converts content (text, images, or structured data) into a high-dimensional vector. Documents with similar meanings have vectors that are close together in this space, enabling semantic search that goes beyond keyword matching.

For example, a document about "machine learning algorithms" would have an embedding similar to one about "AI model training" because they share semantic meaning, even though they use different words.

## Embedding Types

Vertesia supports three types of embeddings, each capturing different aspects of your documents:

### Text Embeddings

Text embeddings capture the semantic meaning of document content. When enabled, Vertesia:

1. Extracts text content from documents (including OCR and vision-based page reading for images and PDFs)
2. Chunks text to fit within model token limits
3. Generates embeddings using the configured model
4. Stores vectors for similarity search

**Use cases:**
- Finding documents with similar content
- Natural language queries
- Content recommendations

### Image Embeddings

Image embeddings (also called vision embeddings) capture visual features of images and document pages. When enabled:

1. Document pages/images are processed by a vision embedding model
2. Visual features are encoded as vectors
3. Enables similarity search based on visual content

**Use cases:**
- Finding visually similar documents
- Image-based search
- Visual content analysis

### Properties Embeddings

Properties embeddings index the structured metadata of documents. This includes:

- Custom properties defined in your schema
- Document type information
- Tags and categories

**Use cases:**
- Finding documents with similar metadata
- Structured data similarity
- Schema-aware search

## Configuration Options

Each embedding type can be configured independently with these settings:

| Setting | Description |
|---------|-------------|
| **environment** | The AI environment to use for generating embeddings |
| **model** | The specific embedding model (e.g., `text-embedding-ada-002`, `text-embedding-3-small`) |
| **dimensions** | The number of dimensions for the embedding vectors |
| **max_tokens** | Maximum tokens per chunk (for text embeddings) |
| **enabled** | Whether this embedding type is active |

### Environment Selection

Select an environment that provides access to embedding models. Common choices include:

- **OpenAI**: `text-embedding-ada-002` (1536 dimensions), `text-embedding-3-small` (1536 dimensions), `text-embedding-3-large` (3072 dimensions)
- **Google Vertex AI**: Various embedding models
- **AWS Bedrock**: Titan and other embedding models

### Dimension Selection

Embedding dimensions affect both quality and performance:

| Dimensions | Trade-offs |
|------------|------------|
| **Lower (256-512)** | Faster search, less storage, potentially lower quality |
| **Medium (1024-1536)** | Good balance of quality and performance |
| **Higher (2048-3072)** | Higher quality, more storage, slower search |

Most projects work well with 1536 dimensions (OpenAI's default).

## Enabling Embeddings via API

### Get Embeddings Status

Check the current status of an embedding type:

```bash {{title: 'cURL'}}
curl --location --request GET \
  'https://api.vertesia.io/api/v1/commands/embeddings/text/status' \
  --header 'Authorization: Bearer <YOUR_JWT_TOKEN>'
```

```typescript {{title: 'Vertesia SDK'}}
import { VertesiaClient } from '@vertesia/client';

const client = new VertesiaClient({
  site: 'api.vertesia.io',
  apikey: '<YOUR_API_KEY>',
});

const status = await client.commands.embeddings.status('text');
console.log(status);
```

**Example Response:**

```json
{
  "status": "idle",
  "embeddingRunsInProgress": 0,
  "totalRunsInProgress": 0,
  "embeddingsModels": ["text-embedding-ada-002"],
  "vectorIndex": {
    "status": "READY",
    "name": "content_abc123",
    "type": "elasticsearch"
  }
}
```

### Enable Embeddings

Activate embeddings for a specific type:

```bash {{title: 'cURL'}}
curl --location --request POST \
  'https://api.vertesia.io/api/v1/commands/embeddings/text/enable' \
  --header 'Authorization: Bearer <YOUR_JWT_TOKEN>' \
  --header 'Content-Type: application/json' \
  --data-raw '{
    "environment": "<ENVIRONMENT_ID>",
    "max_tokens": 500,
    "dimensions": 1536,
    "model": "text-embedding-ada-002"
  }'
```

```typescript {{title: 'Vertesia SDK'}}
import { VertesiaClient } from '@vertesia/client';

const client = new VertesiaClient({
  site: 'api.vertesia.io',
  apikey: '<YOUR_API_KEY>',
});

const response = await client.commands.embeddings.activate('text', {
  environment: '<ENVIRONMENT_ID>',
  max_tokens: 500,
  dimensions: 1536,
  model: 'text-embedding-ada-002',
});
console.log(response);
```

### Disable Embeddings

Deactivate embeddings for a specific type:

```bash {{title: 'cURL'}}
curl --location --request POST \
  'https://api.vertesia.io/api/v1/commands/embeddings/text/disable' \
  --header 'Authorization: Bearer <YOUR_JWT_TOKEN>'
```

```typescript {{title: 'Vertesia SDK'}}
import { VertesiaClient } from '@vertesia/client';

const client = new VertesiaClient({
  site: 'api.vertesia.io',
  apikey: '<YOUR_API_KEY>',
});

const response = await client.commands.embeddings.disable('text');
console.log(response);
```

## Recalculating Embeddings

You may need to recalculate embeddings when:

- Switching to a different embedding model
- Changing dimension settings
- Fixing corrupted or missing embeddings
- Upgrading to a newer model version

### Trigger Recalculation

```bash {{title: 'cURL'}}
curl --location --request POST \
  'https://api.vertesia.io/api/v1/commands/embeddings/text/recalculate' \
  --header 'Authorization: Bearer <YOUR_JWT_TOKEN>'
```

```typescript {{title: 'Vertesia SDK'}}
import { VertesiaClient } from '@vertesia/client';

const client = new VertesiaClient({
  site: 'api.vertesia.io',
  apikey: '<YOUR_API_KEY>',
});

const response = await client.commands.embeddings.recalculate('text');
console.log(response);
```

Recalculation runs as a background process. Monitor progress through the status endpoint.

## Vector Index Status

The vector index status indicates the state of your search index:

| Status | Description |
|--------|-------------|
| **READY** | Index is operational and searchable |
| **PENDING** | Index is being created or updated |
| **FAILED** | Index creation failed (check configuration) |
| **DOES_NOT_EXIST** | No index exists (enable embeddings first) |

## Best Practices

### Model Selection

- **Start with standard models**: `text-embedding-ada-002` or `text-embedding-3-small` work well for most use cases
- **Consider costs**: Higher-dimension models cost more to generate and store
- **Test with your data**: Different models perform better on different content types

### Token Limits

- **500-1000 tokens**: Good for short documents, faster processing
- **2000-4000 tokens**: Better for longer documents, captures more context
- **Consider chunking**: Very long documents are automatically chunked

### Performance Optimization

- Enable only the embedding types you need
- Use appropriate dimensions for your quality requirements
- Monitor index status to ensure embeddings are being generated

## Troubleshooting

### Embeddings Not Generating

1. Check that the embedding type is enabled in project settings
2. Verify the environment has a valid API key
3. Ensure the model is available in your environment
4. Check for quota limits on your AI provider

### Dimension Mismatch Errors

If you change embedding dimensions, you may encounter mismatches:

1. Recalculate all embeddings with the new dimensions
2. Or recreate the vector index (requires reindexing all documents)

### Slow Embedding Generation

- Large backlogs process over time
- Check `embeddingRunsInProgress` in status
- Consider enabling parallel processing in workflows

## Next Steps

- [Search Configuration](/content/search) - Configure search backends
- [Content Overview](/content/overview) - Understand the full search architecture
- [Commands API](/api/commands) - API reference for embedding commands

---

## Markdown Export Guide

Source: https://docs.vertesiahq.com/content/markdown-export
Markdown: https://docs.vertesiahq.com/llms/content/markdown-export.md

This guide explains how to export markdown to `PDF` or `DOCX` with Vertesia rendering jobs.

Use this when you need:

- report exports from stored markdown documents
- workflow message exports from inline markdown
- non-blocking UX (start + poll + download)

TOC behavior:
- The renderer adds a native Table of Contents automatically for both PDF and DOCX.
- Do not add manual TOC tables in markdown (for example `TABLE DES MATIÈRES` with a `Page` column).

## Choose Input Mode

### Object mode (recommended for documents)

Use `object_id` when the markdown already exists as a content object.

Benefits:
- reproducible exports
- easier auditing and reruns
- no large payloads in request body

### Inline mode (recommended for workflow messages)

Use `content` when rendering ephemeral markdown generated during a run (for example message summaries or final answers).

Benefits:
- no need to create an object first
- fast export path for UI message actions

## Rendering Flow

Rendering is asynchronous:

1. `POST /api/v1/rendering/jobs` starts a job.
2. Poll `GET /api/v1/rendering/jobs/status` until terminal status.
3. On `COMPLETED`, download from `download_url` (or fallback to `file_uri`).

Terminal statuses:
- `COMPLETED`
- `FAILED`
- `CANCELED`
- `TERMINATED`
- `TIMED_OUT`

## SDK Usage (Recommended)

The SDK already performs start + polling for you:

```typescript
import { VertesiaClient, MarkdownRenditionFormat } from "@vertesia/client";

const client = new VertesiaClient({
  site: "api.vertesia.io",
  apikey: "<YOUR_API_KEY>",
});

// Object mode
const pdf = await client.store.rendering.render({
  object_id: "<OBJECT_ID>",
  format: MarkdownRenditionFormat.pdf,
  title: "Portfolio Report",
});

// Inline mode (workflow message export)
const docx = await client.store.rendering.render({
  content: "# Message Export\n\nRendered from inline markdown.",
  format: MarkdownRenditionFormat.docx,
  title: "Message Export",
});
```

## Manual Polling (Advanced)

Use manual polling when you need custom behavior (cancel buttons, global queue, telemetry):

```typescript
import { WorkflowExecutionStatus } from "@vertesia/common";

const started = await client.store.rendering.start({
  object_id: "<OBJECT_ID>",
  format: "pdf",
});

while (true) {
  const status = await client.store.rendering.getStatus(
    started.workflow_id!,
    started.workflow_run_id!,
  );

  if (status.status === WorkflowExecutionStatus.COMPLETED) {
    console.log(status.download_url);
    break;
  }

  if (
    status.status === WorkflowExecutionStatus.FAILED ||
    status.status === WorkflowExecutionStatus.CANCELED ||
    status.status === WorkflowExecutionStatus.TERMINATED ||
    status.status === WorkflowExecutionStatus.TIMED_OUT
  ) {
    throw new Error(status.error || "Rendering failed");
  }

  await new Promise((r) => setTimeout(r, 1500));
}
```

## UX Recommendations

- Disable export buttons while a render is running.
- Show a compact “Exporting…” status.
- Surface clear error messages from terminal failure statuses.
- Prefer `object_id` for content views; keep inline only for workflow message export actions.

## Template and Options

Both modes support:
- `template_url`
- `template_logo_url` (studio-hosted URL for PDF template logo override)
- `use_default_template`
- `pandoc_options`
- `metadata` (PDF header/footer fields)

Notes:
- TOC is enabled by default for both PDF and DOCX.
- If needed, disable TOC with `pandocOptions: ["--toc=false"]`.

Example:

```json
{
  "object_id": "<OBJECT_ID>",
  "format": "pdf",
  "metadata": {
    "document_id": "REP-2026-02",
    "agent_name": "Research Agent"
  }
}
```

## API Reference

For complete request/response details, see:
- [/api/rendering](/api/rendering)

---

## Content Indexing Overview

Source: https://docs.vertesiahq.com/content/overview
Markdown: https://docs.vertesiahq.com/llms/content/overview.md

Vertesia provides powerful content indexing and search capabilities that enable AI agents and applications to efficiently find and retrieve relevant documents from your knowledge base.

## What is Content Indexing?

Content indexing is the process of analyzing, embedding, and organizing your documents to make them searchable. Vertesia automatically indexes content when documents are created or updated, maintaining search indexes that support multiple search strategies.

When you upload documents to Vertesia, the platform:

1. **Extracts content** from various file formats (PDF, Word, images, etc.)
2. **Generates embeddings** using AI models to capture semantic meaning
3. **Indexes metadata** including document properties, types, and relationships
4. **Maintains search indexes** for fast retrieval

## Content-derived structure

Vertesia does not require you to define a rigid schema up front and force every document into it. On intake, the platform can read a document and derive its structure from the content itself — assigning a content type and generating a metadata schema that matches what the document actually contains. When an existing type fits, it is reused; when a document needs something new, a new type can be created. Content types are first-class objects, so the structure can evolve with your content rather than being frozen at design time.

This is what lets agents and search work against meaningful, document-specific structure instead of a lowest-common-denominator set of fields.

## Search Infrastructure

Vertesia's search runs on Elasticsearch, which provides vector, full-text, and hybrid search from a single backend:

- **Vector search** using document embeddings for semantic similarity
- **Full-text search** with advanced text analysis, stemming, fuzzy matching, and phrase queries
- **Hybrid search** combining vector and full-text with configurable weights and score fusion
- **Complex aggregations** for analytics and faceted navigation
- **DSL queries** for complete control over search behavior
- **High-volume indexing** with zero-downtime reindexing

You get semantic retrieval, keyword retrieval, and the combination of the two without standing up a separate system for each.

## Search Types

Vertesia supports multiple search strategies that can be used individually or combined:

### Semantic Search (Vector)

Semantic search uses embeddings to find documents based on meaning rather than exact keywords. When you search for "quarterly financial report," semantic search can find documents about "Q3 earnings summary" even without matching words.

**Best for:**
- Finding conceptually similar documents
- Natural language queries
- Cross-language search (when embeddings support it)

### Full-Text Search

Full-text search matches documents based on keywords with support for:

- **Stemming**: Matching "running" with "run," "runs," "ran"
- **Fuzzy matching**: Finding documents despite typos
- **Phrase matching**: Exact phrase requirements
- **Field-specific search**: Targeting specific metadata fields

**Best for:**
- Known keyword searches
- Exact phrase matching
- Technical terminology

### Hybrid Search

Hybrid search combines semantic and full-text search for the best of both approaches. Vertesia supports multiple score aggregation methods:

| Method | Description |
|--------|-------------|
| **RRF** (Reciprocal Rank Fusion) | Combines result rankings, good when relevance scores aren't comparable |
| **RSF** (Relevance Score Fusion) | Combines normalized relevance scores for direct score comparison |
| **Smart** | Automatically selects the best method based on available search types |

You can also configure weights to prioritize one search type over another:

```json
{
  "full_text": "quarterly report",
  "vector": { "text": "financial analysis" },
  "weights": { "full_text": 2, "vector": 3 }
}
```

## What a search returns

Search returns ranked content objects, not loose text fragments. Results can be filtered by document type and properties, and the content store tracks document revisions, so you can work against the current version of a document rather than stale text.

From there, a document's structure is addressable. On intake Vertesia generates a table of contents and a structured representation of each document, so an agent can inspect its sections and headings and fetch a specific one – pulling the *Indemnification* section by its heading, say, rather than the whole file. [Semantic DocPrep](/semantic/overview) additionally preserves layout and bounding boxes, enabling deep links to the exact location a passage came from.

## Getting Started

1. **Configure embeddings** to enable semantic search. See [Embeddings Configuration](/content/embeddings).
2. **Review search configuration and indexing.** See [Search Configuration](/content/search).
3. **Use the search_documents tool** to search from agents. See [Built-in Tools](/agent-runner/tools).

## Next Steps

- [Embeddings Configuration](/content/embeddings) - Configure text, image, and properties embeddings
- [Search Configuration](/content/search) - Configure indexing and search
- [Built-in Tools](/agent-runner/tools) - Learn about the search_documents tool

---

## Search Configuration

Source: https://docs.vertesiahq.com/content/search
Markdown: https://docs.vertesiahq.com/llms/content/search.md

Vertesia's search backend is Elasticsearch, providing vector, full-text, and hybrid search from a single index. This guide covers how to configure and manage it.

## How search works

Documents are indexed in Elasticsearch automatically as they are created and updated, and a single index serves vector, full-text, and hybrid queries:

- **Vector similarity** — HNSW over document embeddings (`text`, `image`, and `properties`) for semantic search
- **Full-text** — stemming, fuzzy matching, and phrase queries
- **Hybrid** — vector and full-text combined, with configurable weights and score fusion (see [Hybrid Search](#hybrid-search))
- **Aggregations** — analytics, facets, and document statistics
- **DSL queries** — full control over search behavior for advanced cases

## Configuration

### Prerequisites

Elasticsearch requires:

1. Elasticsearch infrastructure enabled for your account
2. Embeddings configured (for vector search capabilities)
3. Project settings permission (`project:settings_write`)

### Enabling Elasticsearch

#### Get Status

Check the current Elasticsearch status for your project:

```bash {{title: 'cURL'}}
curl --location --request GET \
  'https://api.vertesia.io/api/v1/indexing/status' \
  --header 'Authorization: Bearer <YOUR_JWT_TOKEN>'
```

```typescript {{title: 'Vertesia SDK'}}
import { VertesiaClient } from '@vertesia/client';

const client = new VertesiaClient({
  serverUrl: 'https://api.vertesia.io',
  apikey: '<YOUR_API_KEY>',
});

const status = await client.store.indexing.status();
console.log(status);
```

**Example Response:**

```json
{
  "infrastructure_enabled": true,
  "indexing_enabled": true,
  "query_enabled": true,
  "index": {
    "exists": true,
    "alias_name": "content_abc123",
    "index_name": "content_abc123_v1",
    "version": 1,
    "created_at": "2024-01-15T10:00:00Z",
    "document_count": 15234,
    "size_bytes": 52428800
  },
  "mongo_document_count": 15234,
  "reindex_in_progress": false,
  "reindex_progress": null
}
```

#### Enable Indexing

Enable Elasticsearch indexing for your project. This creates the index and starts syncing documents:

```bash {{title: 'cURL'}}
curl --location --request POST \
  'https://api.vertesia.io/api/v1/indexing/enable-indexing' \
  --header 'Authorization: Bearer <YOUR_JWT_TOKEN>'
```

```typescript {{title: 'Vertesia SDK'}}
import { VertesiaClient } from '@vertesia/client';

const client = new VertesiaClient({
  serverUrl: 'https://api.vertesia.io',
  apikey: '<YOUR_API_KEY>',
});

const response = await client.store.indexing.enableIndexing();
console.log(response);
```

#### Enable Queries

Queries are now automatically enabled when indexing is enabled. This endpoint is kept for backward compatibility.

```bash {{title: 'cURL'}}
curl --location --request POST \
  'https://api.vertesia.io/api/v1/indexing/enable-queries' \
  --header 'Authorization: Bearer <YOUR_JWT_TOKEN>'
```

```typescript {{title: 'Vertesia SDK'}}
import { VertesiaClient } from '@vertesia/client';

const client = new VertesiaClient({
  serverUrl: 'https://api.vertesia.io',
  apikey: '<YOUR_API_KEY>',
});

const response = await client.store.indexing.enableQueries();
console.log(response);
```

#### Disable Queries

Queries are now automatically enabled when indexing is enabled. To disable queries, disable indexing instead.

```bash {{title: 'cURL'}}
curl --location --request POST \
  'https://api.vertesia.io/api/v1/indexing/disable-queries' \
  --header 'Authorization: Bearer <YOUR_JWT_TOKEN>'
```

```typescript {{title: 'Vertesia SDK'}}
import { VertesiaClient } from '@vertesia/client';

const client = new VertesiaClient({
  serverUrl: 'https://api.vertesia.io',
  apikey: '<YOUR_API_KEY>',
});

const response = await client.store.indexing.disableQueries();
console.log(response);
```

#### Disable Indexing

Disable Elasticsearch indexing entirely:

```bash {{title: 'cURL'}}
curl --location --request POST \
  'https://api.vertesia.io/api/v1/indexing/disable-indexing' \
  --header 'Authorization: Bearer <YOUR_JWT_TOKEN>'
```

```typescript {{title: 'Vertesia SDK'}}
import { VertesiaClient } from '@vertesia/client';

const client = new VertesiaClient({
  serverUrl: 'https://api.vertesia.io',
  apikey: '<YOUR_API_KEY>',
});

const response = await client.store.indexing.disableIndexing();
console.log(response);
```

### Reindexing

Reindexing rebuilds the Elasticsearch index from MongoDB. This may be needed when:

- Enabling Elasticsearch for an existing project with documents
- Changing embedding dimensions
- Index corruption or sync issues
- Recovering from failures

#### Trigger Reindex

```bash {{title: 'cURL'}}
curl --location --request POST \
  'https://api.vertesia.io/api/v1/indexing/reindex' \
  --header 'Authorization: Bearer <YOUR_JWT_TOKEN>' \
  --header 'Content-Type: application/json' \
  --data-raw '{
    "recreate_index": false
  }'
```

```typescript {{title: 'Vertesia SDK'}}
import { VertesiaClient } from '@vertesia/client';

const client = new VertesiaClient({
  serverUrl: 'https://api.vertesia.io',
  apikey: '<YOUR_API_KEY>',
});

const response = await client.store.indexing.reindex(false);
console.log(response);
```

**Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `recreate_index` | boolean | If `true`, drops and recreates the index. Use when changing dimensions or mappings. |

#### Zero-Downtime Reindexing

Vertesia uses alias-based reindexing for zero downtime:

1. A new index is created with updated mappings
2. Documents are batch-indexed to the new index
3. The alias is atomically swapped from old to new
4. The old index is deleted

During reindexing, queries continue to work against the existing index.

#### Monitoring Progress

Check reindex progress through the status endpoint:

```json
{
  "reindex_progress": {
    "status": "running",
    "processed": 5000,
    "total": 15234,
    "current_batch": 10,
    "total_batches": 31,
    "percent_complete": 33
  }
}
```

### Drift Analysis

Use drift analysis to measure how Elasticsearch has diverged from MongoDB without rebuilding the index. The analyzer compares documents by `_id` and `updated_at`.

#### Start Drift Analysis

```bash {{title: 'cURL'}}
curl --location --request POST \
  'https://api.vertesia.io/api/v1/indexing/analyze-drift' \
  --header 'Authorization: Bearer <YOUR_JWT_TOKEN>'
```

```typescript {{title: 'Vertesia SDK'}}
import { VertesiaClient } from '@vertesia/client';

const client = new VertesiaClient({
  serverUrl: 'https://api.vertesia.io',
  apikey: '<YOUR_API_KEY>',
});

const response = await client.store.indexing.analyzeDrift();
console.log(response);
```

#### Check Drift Analysis Status

```bash {{title: 'cURL'}}
curl --location --request GET \
  'https://api.vertesia.io/api/v1/indexing/drift-analysis' \
  --header 'Authorization: Bearer <YOUR_JWT_TOKEN>'
```

```typescript {{title: 'Vertesia SDK'}}
import { VertesiaClient } from '@vertesia/client';

const client = new VertesiaClient({
  serverUrl: 'https://api.vertesia.io',
  apikey: '<YOUR_API_KEY>',
});

const status = await client.store.indexing.getDriftAnalysis();
console.log(status);
```

**Example Completed Response:**

```json
{
  "workflow_id": "analyzeElasticsearchDriftWorkflow:project-abc123",
  "workflow_run_id": "019d1edd-3ac9-7dbf-b78d-470c49771180",
  "status": "COMPLETED",
  "result": {
    "total": 15234,
    "processed": 15234,
    "missing": 8,
    "stale": 3,
    "sample_missing_ids": [
      "65f0c4d7a8f9e7a6e7d3b101"
    ],
    "sample_stale_ids": [
      "65f0c4d7a8f9e7a6e7d3b202"
    ],
    "completed_at": "2026-03-24T08:22:11.000Z"
  }
}
```

## Hybrid Search

Hybrid search combines full-text and vector search for optimal relevance. When both search types return results, scores are aggregated using configurable methods.

### Score Aggregation Methods

| Method | Algorithm | Best For |
|--------|-----------|----------|
| **RRF** | Reciprocal Rank Fusion | When relevance scores from different sources aren't directly comparable |
| **RSF** | Relevance Score Fusion | When you want to combine normalized scores directly |
| **Smart** | Automatic selection | General use, automatically picks the best method |

### Weight Configuration

Control the relative importance of each search type:

```json
{
  "query": {
    "full_text": "quarterly report",
    "vector": { "text": "financial analysis" },
    "weights": {
      "full_text": 2,
      "vector": 3
    }
  }
}
```

Higher weights give more influence to that search type. With the above configuration, vector search results are weighted 1.5x more than full-text results.

### Dynamic Scaling

When enabled, dynamic scaling adjusts weights automatically if one search type is unavailable:

```json
{
  "query": {
    "full_text": "quarterly report",
    "vector": { "text": "financial analysis" },
    "dynamic_scaling": "on"
  }
}
```

## Index Configuration Tools

Agents can query and update index configuration using built-in tools:

### get_index_configuration

Retrieves the current index status and configuration.

**Returns:**
- Index status (exists, healthy)
- Document count and size
- Embedding dimensions for each type
- Field mappings

### update_index_configuration

Updates index configuration with options to change embedding dimensions or trigger reindexing.

**Parameters:**

| Parameter | Type | Description |
|-----------|------|-------------|
| `embedding_dimensions` | object | New dimensions for `text`, `image`, or `properties` |
| `force_reindex` | boolean | Trigger a full reindex |
| `user_confirmed` | boolean | Required confirmation (must use `ask_user` first) |

## Troubleshooting

### Documents Not Appearing in Search

1. Check that indexing is enabled (`indexing_enabled: true`)
2. Verify embeddings are configured and generating
3. Allow time for async indexing to complete
4. Check for sync issues in status endpoint

### Dimension Mismatch Errors

If you changed embedding dimensions:

1. Recalculate embeddings with new dimensions
2. Trigger reindex with `recreate_index: true`

### Search Returns No Results

1. Verify documents exist in MongoDB (`mongo_document_count`)
2. Check Elasticsearch document count matches
3. Test with broader queries or `match_all`
4. Verify query syntax is correct

### Reindex Stuck or Failed

1. Check workflow status in the Vertesia UI
2. Look for errors in workflow history
3. Ensure sufficient permissions
4. Try triggering a new reindex (will cancel stuck one)

## Best Practices

### Index Management

- Enable queries only after initial indexing completes
- Monitor document counts between MongoDB and Elasticsearch
- Schedule reindexing during low-traffic periods

### Search Configuration

- Start with `smart` score aggregation
- Tune weights based on search quality feedback
- Use facets for navigation and filtering
- Enable `analyze` for complex queries that benefit from LLM summarization

### Performance

- Use appropriate `limit` values (avoid fetching more than needed)
- Use `count_only` for pagination totals
- Stream large results to artifacts with `output_artifact`
- Consider DSL mode for complex aggregations

## Next Steps

- [Content Overview](/content/overview) - Understanding the full search architecture
- [Embeddings Configuration](/content/embeddings) - Configure embeddings for vector search
- [Built-in Tools](/agent-runner/tools) - Learn about search_documents and index tools
- [Commands API](/api/commands#indexing-elasticsearch) - Full API reference for indexing endpoints

---

## Dashboards

Source: https://docs.vertesiahq.com/data-platform/dashboards
Markdown: https://docs.vertesiahq.com/llms/data-platform/dashboards.md

Create interactive data visualizations using Vega-Lite, a high-level grammar for building charts. Dashboards are backed by SQL queries and support cross-panel interactivity.

## Dashboard Structure

A dashboard consists of:
- **query**: Single SQL query returning all data needed
- **spec**: Vega-Lite specification defining the visualization

The system automatically injects data and schema - never include `$schema` or `data` in your spec.

## Workflow

Always follow this workflow:

1. **Test query** - Verify SQL returns expected data
2. **Preview** - Use `data_preview_dashboard` to iterate
3. **Verify** - Check labels, colors, layout
4. **Create** - Save with `data_create_dashboard`
5. **Render** - Generate PNG with `data_render_dashboard`

## Chart Types

### Bar Chart

```json
{
  "mark": "bar",
  "encoding": {
    "x": {"field": "category", "type": "nominal"},
    "y": {"field": "value", "type": "quantitative"}
  }
}
```

### Line Chart

```json
{
  "mark": {"type": "line", "point": true},
  "encoding": {
    "x": {"field": "date", "type": "temporal"},
    "y": {"field": "value", "type": "quantitative"}
  }
}
```

### Pie/Donut Chart

```json
{
  "mark": {"type": "arc", "innerRadius": 50},
  "encoding": {
    "theta": {"field": "value", "type": "quantitative"},
    "color": {"field": "category", "type": "nominal"}
  }
}
```

### Scatter Plot

```json
{
  "mark": "point",
  "encoding": {
    "x": {"field": "x_value", "type": "quantitative"},
    "y": {"field": "y_value", "type": "quantitative"},
    "color": {"field": "category", "type": "nominal"},
    "size": {"field": "weight", "type": "quantitative"}
  }
}
```

### Heatmap

```json
{
  "mark": "rect",
  "encoding": {
    "x": {"field": "x_category", "type": "nominal"},
    "y": {"field": "y_category", "type": "nominal"},
    "color": {"field": "value", "type": "quantitative", "scale": {"scheme": "blues"}}
  }
}
```

### Area Chart

```json
{
  "mark": "area",
  "encoding": {
    "x": {"field": "date", "type": "temporal"},
    "y": {"field": "value", "type": "quantitative"}
  }
}
```

## Field Types

| Type | Description | Examples |
|------|-------------|----------|
| `quantitative` | Numbers (continuous) | Sales, temperature, count |
| `nominal` | Categories (unordered) | Region, product type |
| `ordinal` | Categories (ordered) | Rating, size (S/M/L) |
| `temporal` | Dates/times | Order date, timestamp |

## Layout Options

### Vertical Stack (vconcat)

```json
{
  "vconcat": [
    {"title": "Chart 1", "mark": "bar", "encoding": {...}},
    {"title": "Chart 2", "mark": "line", "encoding": {...}}
  ]
}
```

### Horizontal Layout (hconcat)

```json
{
  "hconcat": [
    {"title": "Left", "mark": "bar", "encoding": {...}},
    {"title": "Right", "mark": "arc", "encoding": {...}}
  ]
}
```

### Grid Layout

```json
{
  "concat": [
    {"title": "Chart 1", "mark": "bar", "encoding": {...}},
    {"title": "Chart 2", "mark": "line", "encoding": {...}},
    {"title": "Chart 3", "mark": "point", "encoding": {...}},
    {"title": "Chart 4", "mark": "arc", "encoding": {...}}
  ],
  "columns": 2
}
```

## Cross-Panel Interactivity

The key advantage of combined specs: selections in one panel can filter another.

### Point Selection

Click to select, filter other panels:

```json
{
  "vconcat": [
    {
      "params": [{"name": "regionSelect", "select": {"type": "point", "fields": ["region"]}}],
      "mark": "bar",
      "encoding": {
        "x": {"field": "region", "type": "nominal"},
        "y": {"aggregate": "sum", "field": "amount"},
        "opacity": {"condition": {"param": "regionSelect", "value": 1}, "value": 0.3}
      }
    },
    {
      "transform": [{"filter": {"param": "regionSelect"}}],
      "mark": "line",
      "encoding": {
        "x": {"field": "date", "type": "temporal"},
        "y": {"aggregate": "sum", "field": "amount"}
      }
    }
  ]
}
```

### Brush Selection

Drag to select a range:

```json
{
  "hconcat": [
    {
      "params": [{"name": "brush", "select": {"type": "interval", "encodings": ["x"]}}],
      "mark": "area",
      "encoding": {
        "x": {"field": "date", "type": "temporal"},
        "y": {"aggregate": "sum", "field": "amount"}
      }
    },
    {
      "transform": [{"filter": {"param": "brush"}}],
      "mark": "bar",
      "encoding": {
        "x": {"field": "category", "type": "nominal"},
        "y": {"aggregate": "sum", "field": "amount"}
      }
    }
  ]
}
```

## Tooltips

Always add tooltips for hover context:

```json
{
  "encoding": {
    "tooltip": [
      {"field": "date", "type": "temporal", "title": "Date", "format": "%B %d, %Y"},
      {"field": "revenue", "type": "quantitative", "title": "Revenue", "format": "$,.0f"},
      {"field": "category", "type": "nominal", "title": "Category"}
    ]
  }
}
```

### Number Formatting

| Format | Input | Output |
|--------|-------|--------|
| `,.0f` | 1234567 | 1,234,567 |
| `,.2f` | 1234.5 | 1,234.50 |
| `.1%` | 0.125 | 12.5% |
| `$,.0f` | 1234 | $1,234 |

### Date Formatting

| Format | Output |
|--------|--------|
| `%Y` | 2024 |
| `%B` | January |
| `%b %Y` | Jan 2024 |
| `%B %d, %Y` | January 15, 2024 |

## Color Schemes

### Categorical

```json
{
  "color": {
    "field": "category",
    "type": "nominal",
    "scale": {"scheme": "category10"}
  }
}
```

Available: `category10`, `category20`, `tableau10`, `tableau20`, `set1`, `set2`, `pastel1`

### Sequential

```json
{
  "color": {
    "field": "value",
    "type": "quantitative",
    "scale": {"scheme": "blues"}
  }
}
```

Available: `blues`, `greens`, `oranges`, `reds`, `purples`, `viridis`, `plasma`

### Custom Colors

```json
{
  "color": {
    "field": "status",
    "type": "nominal",
    "scale": {
      "domain": ["low", "medium", "high"],
      "range": ["#22c55e", "#eab308", "#ef4444"]
    }
  }
}
```

## Query Parameters

Make dashboards dynamic with parameterized SQL:

```json
{
  "query": "SELECT * FROM sales WHERE date >= {{start_date}} AND region = {{region}}",
  "queryParameters": {
    "start_date": "CURRENT_DATE - INTERVAL 30 DAY",
    "region": "'US'"
  }
}
```

Override at render time:

```json
{
  "queryParameters": {
    "start_date": "CURRENT_DATE - INTERVAL 90 DAY",
    "region": "'EU'"
  }
}
```

## Transforms

Process data within the visualization:

### Calculate

```json
{
  "transform": [
    {"calculate": "datum.revenue - datum.cost", "as": "profit"}
  ]
}
```

### Filter

```json
{
  "transform": [
    {"filter": "datum.sales > 1000"}
  ]
}
```

### Aggregate

```json
{
  "transform": [
    {
      "aggregate": [
        {"op": "sum", "field": "sales", "as": "total_sales"}
      ],
      "groupby": ["category"]
    }
  ]
}
```

### Window Functions

```json
{
  "transform": [
    {
      "window": [
        {"op": "sum", "field": "sales", "as": "cumulative_sales"}
      ],
      "sort": [{"field": "date"}]
    }
  ]
}
```

## Layering

Overlay multiple marks:

### Line with Points

```json
{
  "layer": [
    {"mark": "line"},
    {"mark": "point"}
  ],
  "encoding": {
    "x": {"field": "date", "type": "temporal"},
    "y": {"field": "value", "type": "quantitative"}
  }
}
```

### Bar with Labels

```json
{
  "layer": [
    {"mark": "bar"},
    {
      "mark": {"type": "text", "dy": -5},
      "encoding": {"text": {"field": "value", "type": "quantitative"}}
    }
  ],
  "encoding": {
    "x": {"field": "category", "type": "nominal"},
    "y": {"field": "value", "type": "quantitative"}
  }
}
```

### Reference Line

```json
{
  "layer": [
    {"mark": "bar", "encoding": {...}},
    {
      "mark": "rule",
      "encoding": {
        "y": {"datum": 10000},
        "color": {"value": "red"},
        "strokeDash": {"value": [4, 4]}
      }
    }
  ]
}
```

## Axes & Legends

### Axis Customization

```json
{
  "encoding": {
    "x": {
      "field": "date",
      "type": "temporal",
      "axis": {"title": "Date", "format": "%b %Y", "labelAngle": -45}
    },
    "y": {
      "field": "revenue",
      "type": "quantitative",
      "axis": {"title": "Revenue ($)", "format": "$,.0f"}
    }
  }
}
```

### Legend Position

```json
{
  "encoding": {
    "color": {
      "field": "category",
      "type": "nominal",
      "legend": {"title": "Category", "orient": "bottom"}
    }
  }
}
```

Positions: `left`, `right`, `top`, `bottom`, `top-left`, `top-right`, `bottom-left`, `bottom-right`

## Versioning

Dashboards support automatic versioning:

### Create Snapshot

```typescript
await dashboardApi.createSnapshot(dashboardId, {
  name: 'q1-final',
  message: 'Finalized Q1 dashboard'
});
```

### List Versions

```typescript
const versions = await dashboardApi.listVersions(dashboardId);
```

### Restore Version

```typescript
await dashboardApi.promoteVersion(dashboardId, versionId);
```

### Toggle Versioning

```typescript
await dashboardApi.setVersioningEnabled(dashboardId, false);
```

## Best Practices

1. **Always add tooltips** - Show values on hover for all charts
2. **Single query** - Use JOINs/CTEs to get all data in one query
3. **Preview first** - Iterate with `data_preview_dashboard` before saving
4. **Use muted colors** - Avoid harsh saturated colors
5. **Clear titles** - Each panel should explain itself
6. **Limit data** - Use SQL LIMIT or `queryLimit` parameter
7. **Test interactivity** - Verify selections filter correctly

## Color Guidelines

Use soft, muted colors:

| Purpose | Recommended | Avoid |
|---------|-------------|-------|
| Success | `#22c55e`, `#4ade80` | `#00ff00` |
| Info | `#3b82f6`, `#60a5fa` | `#0000ff` |
| Danger | `#ef4444`, `#f87171` | `#ff0000` |
| Neutral | `#6b7280`, `#9ca3af` | `#000000` |

## Complete Example

```json
{
  "query": "SELECT date, region, SUM(amount) as revenue FROM sales GROUP BY date, region",
  "spec": {
    "vconcat": [
      {
        "title": "Click region to filter",
        "params": [{"name": "sel", "select": {"type": "point", "fields": ["region"]}}],
        "mark": "bar",
        "encoding": {
          "x": {"field": "region", "type": "nominal"},
          "y": {"aggregate": "sum", "field": "revenue", "type": "quantitative"},
          "opacity": {"condition": {"param": "sel", "value": 1}, "value": 0.3},
          "tooltip": [
            {"field": "region", "type": "nominal"},
            {"aggregate": "sum", "field": "revenue", "type": "quantitative", "format": "$,.0f"}
          ]
        }
      },
      {
        "title": "Revenue trend (filtered)",
        "transform": [{"filter": {"param": "sel"}}],
        "mark": {"type": "line", "point": true},
        "encoding": {
          "x": {"field": "date", "type": "temporal"},
          "y": {"aggregate": "sum", "field": "revenue", "type": "quantitative"},
          "tooltip": [
            {"field": "date", "type": "temporal", "format": "%B %Y"},
            {"aggregate": "sum", "field": "revenue", "type": "quantitative", "format": "$,.0f"}
          ]
        }
      }
    ]
  }
}
```

---

## Getting Started

Source: https://docs.vertesiahq.com/data-platform/getting-started
Markdown: https://docs.vertesiahq.com/llms/data-platform/getting-started.md

This guide walks you through creating your first DataStore, importing data, running queries, and creating a dashboard.

## Prerequisites

- A Vertesia project with the Data Platform plugin installed
- Data files (CSV, JSON, or Parquet) to import
- Appropriate permissions to create DataStores and dashboards

## Step 1: Create a DataStore

A DataStore is a DuckDB database that will hold your structured data.

### Using the UI

1. Navigate to the Data Platform section in your project
2. Click **New Project** to create a data project
3. Enter a name and description for your project
4. The system will create both a Collection (for files) and a DataStore (for structured data)

### Using the API

```typescript
import { VertesiaClient } from '@vertesia/client';

const client = new VertesiaClient({ apiKey: 'your-api-key' });
const dataApi = client.data;

// Create a new DataStore
const store = await dataApi.create({
    name: 'sales-analytics',
    description: 'Sales data for analytics dashboards',
    tags: ['analytics', 'sales'],
});

console.log('Created DataStore:', store.id);
```

## Step 2: Create Tables

Define the schema for your data by creating tables.

### Using the API

```typescript
// Create tables with relationships
const tables = await dataApi.createTables(store.id, {
    tables: [
        {
            name: 'customers',
            description: 'Customer information',
            columns: [
                { name: 'id', type: 'INTEGER', primary_key: true },
                { name: 'name', type: 'STRING', nullable: false },
                { name: 'email', type: 'STRING', semantic_type: 'email' },
                { name: 'created_at', type: 'TIMESTAMP' },
            ],
        },
        {
            name: 'orders',
            description: 'Customer orders',
            columns: [
                { name: 'id', type: 'INTEGER', primary_key: true },
                { name: 'customer_id', type: 'INTEGER', nullable: false },
                { name: 'amount', type: 'DECIMAL', semantic_type: 'currency' },
                { name: 'order_date', type: 'DATE' },
            ],
            foreign_keys: [
                {
                    column: 'customer_id',
                    references_table: 'customers',
                    references_column: 'id',
                    on_delete: 'CASCADE',
                },
            ],
        },
    ],
});
```

### Using AI Schema Creation

In the Data Platform UI, you can upload data files and use AI to automatically generate an optimal schema:

1. Upload your CSV/JSON/Parquet files to the project Collection
2. Click **Create Schema with AI**
3. The AI agent analyzes your files and proposes a schema
4. Review and approve the suggested tables and relationships

## Step 3: Import Data

Import data from various sources into your tables.

### From Inline Data

```typescript
const importJob = await dataApi.import(store.id, {
    tables: {
        customers: {
            source: 'inline',
            data: [
                { id: 1, name: 'Alice', email: 'alice@example.com' },
                { id: 2, name: 'Bob', email: 'bob@example.com' },
            ],
        },
    },
    mode: 'append',
    message: 'Initial customer import',
});
```

### From CSV Files

```typescript
const importJob = await dataApi.import(store.id, {
    tables: {
        orders: {
            source: 'url',
            url: 'https://example.com/data/orders.csv',
            format: 'csv',
        },
    },
    mode: 'replace',
    message: 'Import orders from CSV',
});
```

### Import Modes

- **append**: Add new rows to existing data
- **replace**: Replace all existing data in the table

## Step 4: Query Data

Execute SQL queries against your DataStore using DuckDB syntax.

```typescript
const result = await dataApi.query(store.id, {
    sql: `
        SELECT
            c.name,
            COUNT(o.id) as order_count,
            SUM(o.amount) as total_spent
        FROM customers c
        LEFT JOIN orders o ON c.id = o.customer_id
        GROUP BY c.id, c.name
        ORDER BY total_spent DESC
        LIMIT 10
    `,
    limit: 100,
});

console.log('Columns:', result.columns);
console.log('Rows:', result.rows);
console.log('Execution time:', result.execution_time_ms, 'ms');
```

### Query Features

DuckDB provides powerful analytics capabilities:

- Window functions: `ROW_NUMBER()`, `LAG()`, `LEAD()`, `RANK()`
- Common Table Expressions (CTEs)
- `QUALIFY` clause for filtering window function results
- Pivoting and unpivoting
- JSON functions for semi-structured data

## Step 5: Create a Dashboard

Create a Vega-Lite dashboard to visualize your data.

```typescript
const dashboardApi = dataApi.dashboards(store.id);

const dashboard = await dashboardApi.create({
    name: 'Sales Overview',
    description: 'Key sales metrics and trends',
    query: `
        SELECT
            DATE_TRUNC('month', order_date) as month,
            SUM(amount) as revenue
        FROM orders
        GROUP BY 1
        ORDER BY 1
    `,
    spec: {
        $schema: 'https://vega.github.io/schema/vega-lite/v5.json',
        mark: 'bar',
        encoding: {
            x: { field: 'month', type: 'temporal', title: 'Month' },
            y: { field: 'revenue', type: 'quantitative', title: 'Revenue' },
        },
    },
});

console.log('Created dashboard:', dashboard.id);
```

### Preview Before Saving

Test your visualization without saving:

```typescript
// Preview returns a PNG image
const preview = await dashboardApi.preview({
    query: 'SELECT category, SUM(amount) as total FROM orders GROUP BY category',
    spec: {
        mark: 'arc',
        encoding: {
            theta: { field: 'total', type: 'quantitative' },
            color: { field: 'category', type: 'nominal' },
        },
    },
});
```

## Next Steps

Now that you have the basics, explore more advanced features:

- [Tools Reference](/data-platform/tools) - All Data Platform tools for AI agents
- [Skills Reference](/data-platform/skills) - Reusable skills for data workflows
- [Dashboards Guide](/data-platform/dashboards) - Advanced Vega-Lite visualizations
- [API Reference](/api/data-stores) - Complete REST API documentation

---

## Overview

Source: https://docs.vertesiahq.com/data-platform/overview
Markdown: https://docs.vertesiahq.com/llms/data-platform/overview.md

The **Data Platform** is Vertesia's solution for unified data management, combining AI-powered schema creation, SQL analytics, and interactive dashboards in one integrated experience.

## What is the Data Platform?

The Data Platform enables you to:

- **Manage structured data** using DuckDB-backed DataStores
- **Import data** from CSV, JSON, Parquet, and Excel files
- **Query data** using standard SQL with DuckDB extensions
- **Create dashboards** with Vega-Lite visualizations
- **Analyze data** with AI assistance through specialized agents

## Key Concepts

### DataStores

A DataStore is a DuckDB database that stores your structured data. Each DataStore:

- Contains one or more tables with defined schemas
- Supports SQL queries with DuckDB's powerful analytics extensions
- Provides automatic versioning for schema changes and data imports
- Can be linked to a Collection for file-based data sources

### Tables

Tables within a DataStore have:

- **Columns** with types: STRING, INTEGER, BIGINT, FLOAT, DOUBLE, DECIMAL, BOOLEAN, DATE, TIMESTAMP, JSON
- **Semantic types** for enhanced understanding: email, phone, url, currency, percentage, person_name, address, country, date_iso, identifier
- **Foreign key relationships** with referential integrity
- **Indexes** for query optimization

### Dashboards

Dashboards are Vega-Lite visualizations backed by SQL queries:

- **Single or multi-panel** layouts with vconcat, hconcat, or grid arrangements
- **Interactive selections** for cross-filtering between panels
- **Query parameters** with `{{param}}` syntax for dynamic filtering
- **Automatic versioning** with named snapshots for important states

### Projects

A Project links a Collection (files) with a DataStore (database):

- Files in the Collection serve as data sources
- The DataStore holds the structured, queryable data
- Linked via `dp:<project-slug>` tag
- AI agents can analyze files and automatically create schemas

## Architecture

```
Collection (files)     DataStore (DuckDB)
      │                      │
      └──── dp:<name> tag ───┘
            │
      Project linking
```

- **Tag-based linking**: Collections and DataStores are connected through tags
- **GCS storage**: DataStore files are stored in Google Cloud Storage
- **Versioning**: Automatic versions created on schema changes and imports
- **Snapshots**: Named snapshots protected from automatic cleanup

## Use Cases

### Business Intelligence Dashboards

Create interactive dashboards that visualize key metrics from your data. Combine bar charts, line graphs, and tables to tell a data story.

### Data Analysis with AI

Use AI agents to explore your data, run complex queries, and generate insights. The AI can understand your schema and write appropriate SQL queries.

### ETL and Data Import

Import data from various sources (CSV, JSON, Parquet) with atomic operations. Transform and clean data during import with column mapping and type conversion.

### Schema Design

Let AI analyze your data files and suggest optimal database schemas with appropriate types, relationships, and indexes.

## Next Steps

- [Getting Started](/data-platform/getting-started) - Create your first DataStore and dashboard
- [Tools Reference](/data-platform/tools) - Complete reference for Data Platform tools
- [Skills Reference](/data-platform/skills) - Learn about data-focused agent skills
- [Dashboards](/data-platform/dashboards) - Deep dive into Vega-Lite visualizations

---

## Skills Reference

Source: https://docs.vertesiahq.com/data-platform/skills
Markdown: https://docs.vertesiahq.com/llms/data-platform/skills.md

Skills are reusable capabilities that unlock specialized tools and provide domain-specific knowledge for AI agents. The Data Platform includes five skills for comprehensive data workflows.

## Overview

| Skill | Purpose | Tools Unlocked |
|-------|---------|----------------|
| `data_analysis` | Query and analyze data with SQL/DuckDB | `data_get_schema`, `data_list_tables`, `execute_shell` |
| `data_import` | Import data from files with atomic operations | `data_import`, `execute_shell` |
| `data_migration` | Schema migrations with data transformations | `data_alter_table`, `data_create_tables`, `data_import`, `execute_shell` |
| `data_modeling` | Design database schemas | `data_create_database`, `data_create_tables`, `data_alter_table` |
| `data_visualization` | Create Vega-Lite dashboards | `data_preview_dashboard`, `data_create_dashboard`, `data_update_dashboard`, `data_render_dashboard` |

## data_analysis

Query and analyze persistent data stores using SQL with DuckDB.

### When to Use

- Data is in a persistent data store (created via `data_modeling` + `data_import`)
- SQL-based analytics, reusable queries, or data versioning needed
- Complex analytics like window functions, CTEs, or cohort analysis

### Recommended Approach

Use native DuckDB in the sandbox via `execute_shell` for best performance:

```python
import duckdb

# Connect to synced database
con = duckdb.connect('/home/daytona/databases/my_store.duckdb')

# Run any SQL query
result = con.execute('''
    SELECT customer_id, SUM(amount) as total
    FROM orders
    GROUP BY customer_id
    ORDER BY total DESC
    LIMIT 10
''').fetchdf()

print(result)
```

### Key Features

- **Window Functions**: `ROW_NUMBER()`, `LAG()`, `LEAD()`, `RANK()`
- **CTEs**: Break complex queries into readable parts
- **QUALIFY Clause**: Filter window function results
- **Pivoting**: Transform rows to columns
- **Multi-database queries**: Join across multiple databases

### Example: Cohort Analysis

```sql
WITH first_purchase AS (
  SELECT customer_id, MIN(order_date) as cohort_date
  FROM orders GROUP BY customer_id
)
SELECT
  DATE_TRUNC('month', fp.cohort_date) as cohort,
  DATE_DIFF('month', fp.cohort_date, o.order_date) as months_since,
  COUNT(DISTINCT o.customer_id) as customers
FROM orders o
JOIN first_purchase fp ON o.customer_id = fp.customer_id
GROUP BY 1, 2
```

## data_import

Import data from CSV, JSON, Parquet files or inline data with atomic multi-table support.

### When to Use

- Loading data from files into data stores
- Batch data updates or refreshes
- ETL workflows with data transformation

### Supported Formats

- **CSV**: Comma-separated values with header row
- **JSON**: Array of objects or newline-delimited JSON
- **Parquet**: Columnar format for large datasets

### Import Modes

- **append**: Add new rows to existing data (default)
- **replace**: Clear table before importing

### Data Sources

| Source | URI Format | Example |
|--------|------------|---------|
| inline | (data in `data` field) | Direct JSON array |
| gcs | `gs://bucket/path` | `gs://my-bucket/data.csv` |
| url | `https://...` | `https://example.com/data.csv` |
| artifact | `out/filename` | `out/cleaned_data.csv` |

### Column Normalization

When importing, column names are automatically normalized:
- `Item Type` → `item_type`
- `Order-ID` → `order_id`
- `123_count` → `_123_count`

Create table schemas using normalized names.

### Example: Multi-Table Import

```json
{
  "mode": "replace",
  "message": "Monthly data refresh",
  "tables": {
    "customers": {
      "source": "url",
      "uri": "https://example.com/customers.csv",
      "format": "csv"
    },
    "orders": {
      "source": "gcs",
      "uri": "gs://bucket/orders.parquet",
      "format": "parquet"
    }
  }
}
```

## data_migration

Perform schema migrations with data transformations.

### When to Use Migration vs Alter

| Change | Use |
|--------|-----|
| Add nullable column | `data_alter_table` |
| Drop column | `data_alter_table` |
| Rename column | `data_alter_table` |
| Change column type | **Migration** |
| Split column into two | **Migration** |
| Merge columns | **Migration** |
| Restructure table | **Migration** |

### Migration Workflow

1. **Create snapshot** before migration
2. **Export data** from current table
3. **Transform** with Python/pandas
4. **Update schema** with new structure
5. **Import** transformed data

### Common Patterns

**Change Column Type:**
```python
df['amount'] = pd.to_numeric(df['amount'], errors='coerce')
```

**Split Column:**
```python
df[['first_name', 'last_name']] = df['full_name'].str.split(' ', n=1, expand=True)
```

**Merge Columns:**
```python
df['full_address'] = df['street'] + ', ' + df['city'] + ', ' + df['state']
```

### Safety Best Practices

1. Always create a named snapshot before migration
2. Test on sample data first
3. Validate row counts before and after
4. Know how to rollback from snapshot

## data_modeling

Create databases and design data store schemas with tables, columns, and relationships.

### When to Use

- Creating new data stores
- Designing table schemas
- Defining relationships between tables

### Schema Design Best Practices

**Table Naming:**
- Use snake_case: `customer_orders`
- Use plural names: `customers`, `products`
- Descriptive junction tables: `customer_product_access`

**Column Types:**
- `STRING` - Text data
- `INTEGER` / `BIGINT` - Whole numbers
- `DECIMAL` - Precise decimals (financial data)
- `BOOLEAN` - True/false
- `DATE` / `TIMESTAMP` - Temporal values
- `JSON` - Structured data

**Semantic Types:**
Add hints for AI understanding:
- `email`, `phone`, `url` - Contact info
- `currency`, `percentage` - Financial values
- `person_name`, `address` - Personal info
- `identifier` - IDs and codes

### Example: E-Commerce Schema

```json
{
  "tables": [
    {
      "name": "customers",
      "columns": [
        {"name": "id", "type": "INTEGER", "primary_key": true},
        {"name": "email", "type": "STRING", "semantic_type": "email"},
        {"name": "name", "type": "STRING", "semantic_type": "person_name"}
      ]
    },
    {
      "name": "orders",
      "columns": [
        {"name": "id", "type": "INTEGER", "primary_key": true},
        {"name": "customer_id", "type": "INTEGER"},
        {"name": "amount", "type": "DECIMAL", "semantic_type": "currency"}
      ],
      "foreign_keys": [
        {"column": "customer_id", "references_table": "customers", "references_column": "id"}
      ]
    }
  ]
}
```

## data_visualization

Create persistent Vega-Lite dashboards with SQL-backed data.

### When to Use

- Data is in a persistent data store
- Saved, reusable dashboards needed
- Cross-panel interactivity (selections filter other panels)

### Workflow

1. **Test query** - Verify SQL returns expected data
2. **Preview** - Use `data_preview_dashboard` to iterate
3. **Verify** - Check labels, colors, layout
4. **Create** - Save with `data_create_dashboard`
5. **Render** - Generate PNG with `data_render_dashboard`

### Dashboard Structure

A dashboard consists of:
- **query**: Single SQL query returning all data
- **spec**: Vega-Lite specification (no `$schema` or `data` - injected automatically)

### Layout Options

| Layout | Description |
|--------|-------------|
| `vconcat` | Vertical stack |
| `hconcat` | Horizontal layout |
| `concat` + `columns` | Grid layout |

### Cross-Panel Interactivity

Selections in one panel can filter another:

```json
{
  "vconcat": [
    {
      "params": [{"name": "regionSelect", "select": {"type": "point", "fields": ["region"]}}],
      "mark": "bar",
      "encoding": {
        "x": {"field": "region"},
        "y": {"aggregate": "sum", "field": "amount"}
      }
    },
    {
      "transform": [{"filter": {"param": "regionSelect"}}],
      "mark": "line",
      "encoding": {
        "x": {"field": "date", "type": "temporal"},
        "y": {"aggregate": "sum", "field": "amount"}
      }
    }
  ]
}
```

### Query Parameters

Make dashboards dynamic with parameterized SQL:

```json
{
  "query": "SELECT * FROM sales WHERE date >= {{start_date}}",
  "queryParameters": {
    "start_date": "CURRENT_DATE - INTERVAL 30 DAY"
  }
}
```

### Best Practices

1. **Always add tooltips** - Show values on hover
2. **Single query** - Use JOINs/CTEs for all needed data
3. **Preview first** - Verify before saving
4. **Use muted colors** - Avoid harsh saturated colors
5. **Clear titles** - Each panel should explain itself

## Skill Combinations

Skills work together for complete workflows:

**Data Pipeline:**
1. `data_modeling` - Create schema
2. `data_import` - Load initial data
3. `data_analysis` - Query and explore
4. `data_visualization` - Create dashboards

**Schema Evolution:**
1. `data_modeling` - Initial schema
2. `data_analysis` - Identify needed changes
3. `data_migration` - Transform schema and data

**Reporting Workflow:**
1. `data_analysis` - Develop queries
2. `data_visualization` - Create dashboard
3. `data_analysis` - Refine based on feedback

## Related Documentation

- [Tools Reference](/data-platform/tools) - Detailed tool parameters
- [Dashboards Guide](/data-platform/dashboards) - Vega-Lite deep dive
- [API Reference](/api/data-stores) - REST API documentation

---

## Tools Reference

Source: https://docs.vertesiahq.com/data-platform/tools
Markdown: https://docs.vertesiahq.com/llms/data-platform/tools.md

The Data Platform provides a comprehensive set of tools for AI agents to manage data stores, tables, queries, and dashboards. Tools are organized into categories based on their function.

## Read Tools

These tools are enabled by default and provide read-only access to data stores.

### data_get_schema

Get the schema of a data store including table definitions, columns, and relationships.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `store_id` | string | Yes | The ID of the data store |
| `format` | string | No | `'full'` for complete details, `'data'` for AI-optimized summary (default) |

**Returns:** Schema with tables, columns, relationships, and metadata.

### data_list_tables

List all tables in a data store with metadata.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `store_id` | string | Yes | The ID of the data store |

**Returns:** Array of tables with column counts and row counts.

### data_list_dashboards

List dashboards in a data store with optional status filtering.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `store_id` | string | Yes | The ID of the data store |
| `status` | string | No | Filter by status: `'active'` or `'archived'` |

**Returns:** Array of dashboard summaries.

### data_list_dashboard_versions

List version history for a dashboard.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `store_id` | string | Yes | The ID of the data store |
| `dashboard_id` | string | Yes | The ID of the dashboard |
| `limit` | number | No | Maximum versions to return |

**Returns:** Array of version records with timestamps and snapshot names.

## Write Tools

These tools are disabled by default and must be unlocked by skills. They modify data stores.

### data_create_database

Create a new DuckDB database for storing analytical data.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `name` | string | Yes | Database name (lowercase with underscores) |
| `summary` | string | No | Description of the database purpose |

**Returns:** Created data store with ID.

### data_create_tables

Create one or more tables atomically in a single transaction.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `store_id` | string | Yes | The ID of the data store |
| `tables` | array | Yes | Array of table definitions |
| `message` | string | Yes | Commit message for version history |

**Table Definition:**

```json
{
  "name": "customers",
  "description": "Customer information",
  "columns": [
    {
      "name": "id",
      "type": "INTEGER",
      "primary_key": true
    },
    {
      "name": "email",
      "type": "STRING",
      "semantic_type": "email",
      "nullable": false
    }
  ],
  "foreign_keys": [
    {
      "column": "account_id",
      "references_table": "accounts",
      "references_column": "id",
      "on_delete": "CASCADE"
    }
  ]
}
```

**Column Types:** `STRING`, `INTEGER`, `BIGINT`, `FLOAT`, `DOUBLE`, `DECIMAL`, `BOOLEAN`, `DATE`, `TIMESTAMP`, `JSON`

**Semantic Types:** `email`, `phone`, `url`, `currency`, `percentage`, `person_name`, `address`, `country`, `date_iso`, `identifier`

### data_alter_table

Modify an existing table schema. Creates a version snapshot before changes.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `store_id` | string | Yes | The ID of the data store |
| `table_name` | string | Yes | The table to alter |
| `add_columns` | array | No | Columns to add |
| `drop_columns` | array | No | Column names to remove |
| `rename_columns` | array | No | Columns to rename (`{from, to}`) |
| `modify_columns` | array | No | Column modifications |

### data_import

Import data into one or more tables atomically. Creates a version snapshot before import.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `store_id` | string | Yes | The ID of the data store |
| `tables` | object | Yes | Map of table names to import configs |
| `mode` | string | No | `'append'` (default) or `'replace'` |
| `message` | string | No | Description for version history |

**Import Sources:**

- **inline**: Data provided directly in the `data` field
- **gcs**: Google Cloud Storage path (`gs://bucket/path`)
- **url**: HTTPS URL to file
- **artifact**: Sandbox output file (`out/filename.csv`)

**Example:**

```json
{
  "store_id": "store123",
  "tables": {
    "customers": {
      "source": "inline",
      "data": [
        {"id": 1, "name": "Alice"},
        {"id": 2, "name": "Bob"}
      ]
    },
    "orders": {
      "source": "url",
      "uri": "https://example.com/orders.csv",
      "format": "csv"
    }
  },
  "mode": "append",
  "message": "Initial data import"
}
```

### data_query (Deprecated)

Execute SQL queries against a data store. Prefer using native DuckDB via `execute_shell` for better performance.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `store_id` | string | Yes | The ID of the data store |
| `sql` | string | Yes | SQL query to execute |
| `params` | object | No | Query parameters for `{{param}}` placeholders |
| `limit` | number | No | Max rows to return (default: 100, max: 10000) |

## Dashboard Tools

Tools for creating and managing Vega-Lite visualizations.

### data_preview_dashboard

Preview a dashboard without saving. Renders to PNG for iteration.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `store_id` | string | Yes | The ID of the data store |
| `query` | string | Yes | SQL query for dashboard data |
| `spec` | object | Yes | Vega-Lite specification |
| `queryLimit` | number | No | Max rows (default: 10000) |
| `queryParameters` | object | No | Default values for `{{param}}` placeholders |
| `scale` | number | No | Scale factor (default: 1, use 2 for retina) |
| `backgroundColor` | string | No | Background color (default: `'#ffffff'`) |

**Returns:** PNG image of the rendered dashboard.

### data_create_dashboard

Create a new saved dashboard. Use `data_preview_dashboard` first to iterate on design.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `store_id` | string | Yes | The ID of the data store |
| `name` | string | Yes | Dashboard name (unique within store) |
| `summary` | string | No | Description of the dashboard |
| `query` | string | Yes | SQL query for dashboard data |
| `spec` | object | Yes | Vega-Lite specification |
| `queryLimit` | number | No | Max rows (default: 10000) |
| `queryParameters` | object | No | Default parameter values |

### data_update_dashboard

Update an existing dashboard.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `store_id` | string | Yes | The ID of the data store |
| `dashboard_id` | string | Yes | The dashboard to update |
| `name` | string | No | New name |
| `summary` | string | No | New description |
| `query` | string | No | New SQL query |
| `spec` | object | No | New Vega-Lite specification |

### data_render_dashboard

Render a saved dashboard to PNG.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `store_id` | string | Yes | The ID of the data store |
| `dashboard_id` | string | Yes | The dashboard to render |
| `queryParameters` | object | No | Override default parameters |
| `scale` | number | No | Scale factor |

### data_snapshot_dashboard

Create a named snapshot of the current dashboard state.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `store_id` | string | Yes | The ID of the data store |
| `dashboard_id` | string | Yes | The dashboard to snapshot |
| `name` | string | Yes | Snapshot name |
| `message` | string | No | Description of this snapshot |

### data_promote_dashboard_version

Restore a specific version as the current dashboard state.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `store_id` | string | Yes | The ID of the data store |
| `dashboard_id` | string | Yes | The dashboard |
| `version_id` | string | Yes | The version to promote |

### data_set_dashboard_versioning

Enable or disable automatic versioning for a dashboard.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `store_id` | string | Yes | The ID of the data store |
| `dashboard_id` | string | Yes | The dashboard |
| `enabled` | boolean | Yes | Whether versioning is enabled |

## Best Practices

### Atomic Operations

All schema changes and imports are atomic:
- Creating multiple tables happens in a single transaction
- Multi-table imports succeed or fail together
- Rollback on any failure preserves data integrity

### Versioning

- Automatic versions created on schema changes and imports
- Named snapshots protected from 30-day TTL cleanup
- Use snapshots for important milestones
- Rollback available for any version

### Query Optimization

For complex analytics, use native DuckDB via `execute_shell`:

```sql
-- Window functions
SELECT *, ROW_NUMBER() OVER (PARTITION BY category ORDER BY amount DESC) as rank
FROM orders;

-- QUALIFY clause
SELECT * FROM orders
QUALIFY ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY order_date DESC) = 1;
```

### Dashboard Workflow

1. Test query separately to verify data
2. Use `data_preview_dashboard` to iterate on visualization
3. Verify the preview looks correct
4. Use `data_create_dashboard` to save
5. Use `data_render_dashboard` to generate final output

---

## Configuration

Source: https://docs.vertesiahq.com/environments
Markdown: https://docs.vertesiahq.com/llms/environments.md

Vertesia is a bring your own key service which supports many model inference service providers such as OpenAI, Google Vertex AI, AWS Bedrock and many others. Users can configure and use several providers of their choice, either by using Studio, the REST API or the SDK.

Please refer to each provider documentation page to learn how to use it with Vertesia:

* [GCP Vertex AI](/environments/gcp)
* [AWS Bedrock](/environments/aws)
* [OpenAI](/environments/openai)

For AWS Bedrock and GCP VertexAI, helper scripts to automate and the configuration are available [here](https://github.com/vertesia/composableai/tree/main/tools/environment-configuration).

## Vertesia Execution Environments

Vertesia introduces the concept of Execution Environment. An environment is configured to use an inference provider and contains the individual models that are available. Vertesia also provides managed environments, including AWS Bedrock and Google Vertex AI, that you can use to get started quickly and try out the platform — just accept the default setting when creating your new project.

![Environment List](/environments/environment_list.png)

## Execution Environment

An Execution Environment attributes include:
* An inference service provider
* An API key
* An Endpoint URL
* Available models from the provider that can be enabled
* Enabled models
* Projects within the user's Vertesia organization that can use environment

![Environment Detail](/environments/environment_detail.png)

## Code Execution Environment (Daytona Sandbox)

For code execution and advanced agent workflows, Vertesia uses managed sandboxes provided by Daytona. These sandboxes are orchestrated by the `execute_shell` tool and are created on demand for each workflow run.

To enable Daytona-based code execution:

- Configure a `DAYTONA_API_KEY` secret in your environment using your secret provider.
- Optionally set `DAYTONA_API_URL` to point to a custom Daytona deployment (defaults to `https://app.daytona.io/api`).

When an agent first calls `execute_shell`, Vertesia:

- Creates or reuses a sandbox associated with the workflow run.
- Syncs scripts, data files, skills, and documents into the sandbox.

On workflow completion or failure, Vertesia automatically stops Daytona sandboxes for that run to release resources.

---

## AWS Bedrock

Source: https://docs.vertesiahq.com/environments/aws
Markdown: https://docs.vertesiahq.com/llms/environments/aws.md

Vertesia uses Workload Identity Federation to access AWS services, and act as an OIDC Identity Provider for AWS.

In practice this means that Vertesia presents a Vertesia-generated, cryptographically-signed token (JWT), that is verified by AWS Secure Token Service, and then STS issues in exchange a short lived access token that assign the role and permissions defined in the AWS configuration.

Before you start: please make sure you have enabled models in Bedrock. See: [Manage access to Amazon Bedrock foundation models](https://docs.aws.amazon.com/bedrock/latest/userguide/model-access.html).

In addition to this documentation page, a helper script which automates all the configuration steps is available [here](https://github.com/vertesia/composableai/tree/main/tools/environment-configuration).

## Configure OIDC Provider

* Login to the AWS Console
* Go to **IAM** and select **Identity Providers**

![AWS Identity Provider](/environments/aws/aws_identity_provider.png)

* Set Provider to **https://sts.becomposable.com** and the Audience to **bedrock**. You can add any tag, to match your company policies or preferences.

![AWS Identity Provider](/environments/aws/aws_add_identity_provider.png)

You now have configured the identity provider to verify and decode tokens created by Vertesia.

To configure the access to Bedrock, you simply need to create a role, and authorize your Vertesia Environment to access Bedrock, as Vertesia generates an environment-specific token each time it’s calling the Bedrock API. If you plan to have multiple Vertesia Environments for Bedrock, or run Bedrock in multiple AWS regions (which requires multiple Vertesia Environments), then this must be configured for each Vertesia Environment.

## Configure a new Vertesia Execution Environment

* Open [Vertesia Studio](https://cloud.vertesia.io/studio/)
* First, copy your Organization ID as it will be required in the following steps. You can display your Organization ID by clicking on the user info icon in the top right corner of the Studio UI

![User Info](/environments/aws/vertesia_organization_id.png)

* Go to **Environments**
* Create a new Environment, select **bedrock** as Provider, put the **Bedrock region in Endpoint URL**

![AWS Identity Provider](/environments/aws/aws_vertesia_new_env.png)

* Take not of the environment ID

![Vertesia Environment ID](/environments/aws/aws_vertesia_env_id.png)


## Configure an AWS IAM Role

* Open the AWS Console
* Go to **IAM**, select **Roles**
* Create a new Role (e.g. Vertesia Environment X)
* Select Web Identity as Trusted Entity Type, select the newly created **sts.becomposable.com** Identity Provider, and the Audience bedrock.

![Vertesia Environment ID](/environments/aws/aws_role_config.png)

* Add Condition:
  * Key: **sts.becomposable.com:sub**
  * Condition **StringEquals**
  * Value: **env:ORGANIZATION_ID:ENVIRONMENT_ID**

 You need to replace the values ORGANIZATION_ID with your Vertesia Organization ID, and ENVIRONMENT_ID with the Environment ID in Vertesia that will access the account.

* Select or Create a Policy that gives access, for example **AmazonBedrockFullAccess**, or you can create a more narrow one depending on the use case for this environment.
* Enter a Role Name, then review and create the Role
* Select the new role created, and take note of the Role ARN now displayed, in this case:
**arn:aws:iam::YOUR_AWS_ACCOUNT_ID:role/VertesiaEnvironmentX**

## Finalize the Vertesia Execution Environment Configuration

* Go to **Environments**
* Select the Environment you have previously created
* Set the API Key to the Role ARN of the role in AWS, in this example:  **arn:aws:iam::YOUR_AWS_ACCOUNT_ID:role/VertesiaEnvironmentX**
* Refresh the page.
* You should now be able to see models listed from your Bedrock environment.

---

## GCP Vertex AI

Source: https://docs.vertesiahq.com/environments/gcp
Markdown: https://docs.vertesiahq.com/llms/environments/gcp.md

Vertesia uses Workload Identity Federation to access GCP services, and act as an OIDC Identity Provider for GCP.

In practice this means that Vertesia present a Vertesia-generated, cryptographically-signed token (JWT), that is verified by GCP Secure Token Service, and then STS issues in exchange a short lived access token that assign the role and permissions defined in IAM.

Thanks to this approach, you can set fine grained permissions on any environment or account in Vertesia.

In addition to this documentation page, a helper script which automates all the configuration steps is available [here](https://github.com/vertesia/composableai/tree/main/tools/environment-configuration)

## Create an Identity Pool

If you want to add a provider to an existing pool, skip this step and go directly to the next section

* Open the [Google Cloud Console](https://console.cloud.google.com/)
* Go to Workload Identity Pools
* Create a new pool (you can use any name)

![GCP Create Pool](/environments/gcp/gcp_new_pool.png)

## Configure a Provider

* Provider: OpenID Connect (OIDC)
* Name: vertesia
* Issuer URL: https://sts.becomposable.com
* Copy the `default audience url`

![GCP Add provider](/environments/gcp/gcp_provider.png)

* Configure the provider attributes to map Vertesia’s token attributes to the Google’s principal attributes:

| Google Attributes | OIDC attributes |
| ---- | ---- |
| google.subject | assertion.sub |
| attribute.account | assertion.account.id |
| attribute.project | assertion.project.id |
| attribute.name| assertion.name |

![GCP Attribute Mapping](/environments/gcp/gcp_attribute_mapping.png)

On the pool details page, after the creation, copy the `IAM principal value`:

`principal://iam.googleapis.com/projects/PROJECT_ID/locations/global/workloadIdentityPools/POOL_NAME/subject/SUBJECT_ATTRIBUTE_VALUE`

## Grant Permissions

You now need to give permissions to your Vertesia identity pool access to VertexAI.

* Go to IAM
* Click on Grant Access to set a role on a principal
* Set the principal to the URL you noted above, adding your Vertesia account ID: `principal://iam.googleapis.com/projects/PROJECT_ID/locations/global/workloadIdentityPools/POOL_NAME/attribute.account/ORGANIZATION_ID`
  * To get the value for ORGANIZATION_ID, Open [Vertesia Studio](https://cloud.vertesia.io/studio/)
  * You can display your Organization ID by clicking on the user info icon in the top right corner of the Studio UI
  ![User Info](/environments/aws/vertesia_organization_id.png)
* Select **Vertex AI User** and **Service Account Token Creator** as a role, or a custom role if you have created one

Other principal formats can be use to make access more restrictive:
* Only a specific execution environment:
  `principal://iam.googleapis.com/projects/PROJECT_ID/locations/global/workloadIdentityPools/POOL_NAME/subject/env:ORGANIZATION_ID:ENVIRONMENT_ID`
* Only request coming from a specific project:
  `principalSet://iam.googleapis.com/projects/PROJECT_ID/locations/global/workloadIdentityPools/POOL_NAME/attribute.project/PROJECT_ID`

## Configure a new Vertesia Execution Environment

In Vertesia, go to Environments, you can now add a new environment for the provider Google Vertex AI, and set the following:
* API Key: set `the default audience URL` that you have noted in [Configure a Provider](#configure-a-provider)
* Endpoint: set it to `REGION_NAME:GOOGLE_PROJECT_NAME`, for example `us-central1:myvertesiaproject`

![Execution Environment](/environments/gcp/gcp_execution_env.png)

Save, refresh the page. Models should now list, and you can enable models in your environment.

---

## Model Deprecation

Source: https://docs.vertesiahq.com/environments/model-deprecation
Markdown: https://docs.vertesiahq.com/llms/environments/model-deprecation.md

Model deprecation means that certain models may be phased out or become unavailable as providers update their offerings.
Check the official deprecation documentation on your platform (VertexAI, Bedrock, etc) for the latest information about supported models and timelines.

For major models such as Anthropic it can be prudent to check their deprecation schedules, even if you are using them via third-party platforms.
Exact deprecation dates may vary by platform and do not always align with the listed dates.

Below are direct links to model deprecation documentation for platform and model providers:

## Model Deprecation Links

**Anthropic**: [Anthropic Model Deprecations](https://docs.claude.com/en/docs/about-claude/model-deprecations)
**AWS Bedrock**: [Bedrock Model Lifecycle](https://docs.aws.amazon.com/bedrock/latest/userguide/model-lifecycle.html)
**Azure Foundry**: [Azure AI Foundry Model Lifecycle & Retirement](https://learn.microsoft.com/en-us/azure/ai-foundry/concepts/model-lifecycle-retirement)
**Azure OpenAI**: [Azure OpenAI Model Retirements](https://learn.microsoft.com/en-us/azure/ai-foundry/openai/concepts/model-retirements)
**Google Vertex AI**: [Vertex AI Model Deprecations](https://cloud.google.com/vertex-ai/generative-ai/docs/deprecations)
**Groq Cloud**: [Groq Model Deprecation](https://console.groq.com/docs/deprecations)
**HuggingFace Inference Endpoint**: [HuggingFace Inference Endpoints](https://huggingface.co/) (see API docs for endpoint/model status)
**IBM WatsonX**: [IBM Foundation Model Lifecycle](https://dataplatform.cloud.ibm.com/docs/content/wsj/analyze-data/fm-model-lifecycle.html?context=wx)
**Mistral AI**: [Mistral AI Models](https://docs.mistral.ai/getting-started/models#legacy-models)
**OpenAI**: [OpenAI Model Deprecations](https://platform.openai.com/docs/deprecations)
**Replicate**: [Replicate Documentation](https://replicate.com/) (model status and deprecation are noted per model)
**Together AI**: [Together AI Deprecations](https://docs.together.ai/docs/deprecations)

Refer to their official documentation. Staying informed will help you avoid disruptions due to model deprecation and ensure you are using the most up-to-date models for your projects.

---

## OpenAI

Source: https://docs.vertesiahq.com/environments/openai
Markdown: https://docs.vertesiahq.com/llms/environments/openai.md

To use OpenAI with Vertesia, first generate an [API Key in you OpenAI dashboard](https://platform.openai.com/api-keys).

Next, go to Environments and configure a new Execution Environment:

* Give your environment a name
* Set `OpenAI` in Provider
* Leave the endpoint URL empty
* paste your API key

![Environment Detail](/environments/openai/openai.png)

---

## Errors

Source: https://docs.vertesiahq.com/errors
Markdown: https://docs.vertesiahq.com/llms/errors.md

In this guide, we will talk about what happens when something goes wrong while you work with the API. Let's look at some status codes and error types you might encounter. {{ className: 'lead' }}

You can tell if your request was successful by checking the status code when receiving an API response. If a response comes back unsuccessful, you can use the error type and error message to figure out what has gone wrong and do some rudimentary debugging (before contacting support).

---

## Status codes

Here is a list of the different categories of status codes returned by the API. Use these to understand if a request was successful.

    A 2xx status code indicates a successful response.
    A 4xx status code indicates a client error — this means it's a _you_
    problem.
    A 5xx status code indicates a server error — you won't be seeing these.

---

## Interactions Execution Errors


    Whenever an execution is unsuccessful, the API will return an error response with an error type and message.
    You can use this information to understand better what has gone wrong and how to fix it.
    Most of the error messages are pretty helpful and actionable.

    After an execution error occurs, the Run status is set to `failed` and the execution is stopped.
    The error message is stored in the `error` field of the Run object.


    ```json {{ title: "Error response" }}
    {
      "code": "error",
      "message": "429 Rate limit reached for gpt-4 in organization on tokens per min.
      Limit: 10000 / min.
      Please try again in 6ms.
      Contact us through our help center at help.openai.com if you continue to have issues."
      }
    ```

---

## Agent nodes

Source: https://docs.vertesiahq.com/processes/agent-nodes
Markdown: https://docs.vertesiahq.com/llms/processes/agent-nodes.md

An **agent node** delegates an open-ended sub-task to a child conversation workflow that can call tools. The parent process still owns control flow: the agent is a worker, not an orchestrator. This page covers the exact contract between the engine and the agent so you can author agent nodes that actually behave.

## The contract, in one sentence

> Given a prompt, a result schema, and a tool set, produce a single JSON object matching the schema. The engine does the rest.

The agent does **not** pick the next node via tool calls, does **not** write context via tool calls, and does **not** decide its own termination. It produces a value.

## What the engine provides to the agent

When the engine dispatches an agent node, it injects the following into the child conversation:

1. **A system/user prompt** (`sys:ProcessAgentNode` interaction by default) containing:
    - The process name and description.
    - The node id, the node's `prompt`, and the node's declared writes.
    - The current process context (serialized).
    - The available agent-triggered transitions (if any).
    - A human-readable description of the result schema.
2. **A derived `result_schema`** — a JSON Schema built from `node.writes` filtered through `process.context.schema.properties`. If the node has more than one agent-triggered transition, the schema also requires a `_next_node` field whose enum is the set of declared targets.
3. **The declared tools** from `node.tools`, plus any `learn_*` skills. Tools declared at node level are added to whatever the default tool set contains.

Agents do **not** receive process-control tools such as `set_context`, `transition_to`, `skip_node`, `continue_process`, `retry_node`, or `fail_process` — those belong to the higher-level supervised orchestrator and would be rejected by the result schema anyway.

## What the agent must return

A single JSON object that matches the result schema. Llumiverse drivers honour `result_schema` via native structured output when available, and the conversation workflow returns that structured block in its final output.

For a node with `writes: ["parties", "total_value"]` and a single agent transition `flag_clauses`, the schema is roughly:

```json
{
    "type": "object",
    "properties": {
        "parties": { "type": "string" },
        "total_value": { "type": "number" }
    },
    "required": ["parties", "total_value"],
    "additionalProperties": false
}
```

And a valid final response:

```json
{ "parties": "ACCOR SA and Vertesia SAS", "total_value": 97500 }
```

The engine auto-transitions to `flag_clauses` because the node has exactly one agent-triggered transition.

For a node with two agent-triggered transitions, the schema additionally requires `_next_node`:

```json
{
    "type": "object",
    "properties": {
        "classification": { "type": "string" },
        "_next_node": { "type": "string", "enum": ["human_review", "auto_publish"] }
    },
    "required": ["classification", "_next_node"],
    "additionalProperties": false
}
```

The `_next_node` value picks the transition.

## What happens on the parent side

1. The parent reads the child conversation's final output and looks for a structured block (`type: "json"` from the driver, or a JSON-parseable text block as a fallback).
2. It splits the result into `{ _next_node, ...writes }`.
3. It applies `writes` through the validator — rejecting anything outside `node.writes` and anything that violates the context schema.
4. It picks the transition:
    - If `_next_node` is present, that target must be a declared agent transition (or the engine errors).
    - Otherwise, if there's exactly one agent-triggered transition, it's auto-picked.
    - Otherwise, there's no target and the node fails.
5. It checkpoints context to Mongo and moves on.

## Failure behavior

The engine fails the node rather than auto-advancing when:

- A schema was declared but the child returned no parseable structured output matching it.
- The parsed `_next_node` is not in the declared enum.
- The returned writes violate `process.context.schema` or reference fields outside `node.writes`.

A failing node leaves the run in `failed` status with the node history entry marked `completed` up to the point of exit (so you can inspect what the child conversation actually did in the **Conversation** tab).

## Tools and skills

Declare tools the agent needs in `node.tools`:

```json
"extract_terms": {
    "type": "agent",
    "tools": ["fetch_document"],
    "writes": ["parties", "term_length"],
    ...
}
```

Skills (`learn_*`) are available by default so the agent can self-unlock capabilities. If you want a leaner tool set, explicit names without a `+`/`-` prefix replace the default; names prefixed with `+` are added to it, `-` removes.

## Artifact isolation

Each agent-node child conversation is launched with `launch_id = "-"`. That maps to a per-node artifact namespace under `agents//workstreams/<launch_id>/...` so sibling nodes never overwrite each other's `tools.json`, `conversation.json`, or generated artifacts.

## Authoring tips

- Always fill in `human_description` so the observability view can tell a human reader what the node does.
- Keep `node.writes` tight — the agent only sees and emits these fields, which both reduces hallucination surface and makes validation strict.
- Prefer single-transition nodes; push branching into dedicated `condition` nodes. Multi-transition agent nodes are harder to reason about and require the agent to decide routing.
- Reference context fields in `node.prompt` using `{{field}}` — the engine expands inputs against the context before dispatch. Missing fields throw at template time, so only reference fields guaranteed to exist.

---

## Authoring processes

Source: https://docs.vertesiahq.com/processes/authoring
Markdown: https://docs.vertesiahq.com/llms/processes/authoring.md

A process is a JSON document. You can write it by hand, generate it with the **Studio Assistant** (which loads the `learn_process_design` skill), or iterate with an LLM in any editor and paste the result. This page covers both paths and the rules a valid definition must satisfy.

## The editor in Vertesia Studio

Studio gives you both a visual authoring surface and a code editor for the same definition:

- **Statechart / process board.** The visual view renders the process as cards and transitions. For larger definitions, use lanes and phases so the graph reads like a business process board instead of a raw dependency graph.
- **Inspector.** Clicking a node or transition opens the selected definition as YAML, plus readable fields such as type, tool, interaction, writes, phase, lane, and order.
- **Explain.** The inspector can call `sys:ExplainProcess` for the selected node, guard, or whole process. Use this before changing an unfamiliar definition.
- **Code tab.** The full definition is still editable as JSON/YAML when you need bulk changes, search/replace, or a reviewable diff.
- **Live parse.** Save is blocked while the document is unparseable.
- **Validation.** The save path runs the same server-side process-definition validation used by the `validate_process` tool. The Studio Assistant calls that tool directly while iterating.

The stored definition contract is explicit about its schema generation: native process definitions carry `format_version: 1`. Studio can prefill that field for a new draft, but the saved JSON keeps it present so future migrations have a stable boundary.

## Authoring with the Studio Assistant

The fastest way to go from "I want a process that does X" to a committed draft is the **Studio Assistant**. When you ask it to author or change a process, it loads the `learn_process_design` skill — which unlocks the process tools and grammar before it drafts anything:

- `learn_process_design` — loads the full process grammar and authoring rules before drafting.
- `list_tools`, `list_interactions`, `get_interaction` — confirm every name it references actually exists.
- `validate_process` — validates a draft JSON against the schema. The assistant iterates until clean.
- `create_process` / `update_process` — write or revise a draft in the catalog.
- `publish_process` — publishes a draft only after explicit confirmation.
- `start_process_run`, `get_process_run` — run and inspect a draft to test it end to end.
- `LayoutProcessDefinition` — improves visual metadata and transition labels without changing runtime behavior.
- `ask_user`, `plan`, `update_plan`, `think` — standard authoring scaffolding.

Its iteration loop is:

1. Discover — one `ask_user` batch with all open questions.
2. Load the grammar (`learn_process_design`).
3. Confirm tool / interaction names with the user before referencing them.
4. Draft the JSON.
5. Validate; fix every error; repeat until clean.
6. Create or update a draft. Do not set `status` or `version`; the server owns them.
7. Publish only after explicit confirmation. Include an optional publish comment when one is provided.
8. Summarize: node graph, inputs expected per run, outputs written to context, where humans intervene.

### Rules a valid definition must satisfy

These are the traps that catch first-time authors. They're codified in the `learn_process_design` skill and in the server-side validator:

1. **`node.input` is a runtime mapping, not a schema.**
   Use a bare template: `"invoice": "{{invoice_doc_id}}"`. Never put a JSON-schema fragment like `{ "type": "string", "editor": "document" }` inside `node.input` — that belongs on the target interaction's input schema.
2. **Document fields in `context.schema` must set both `format` and `editor`.**
   Use `{ "type": "string", "format": "document", "editor": "document", "description": "…" }`. Without `editor`, the Start Run modal falls back to a raw `store:` text field.
3. **Agent nodes return structured output, not control-flow tool calls.**
   Don't prompt worker agents to call process-control tools such as `set_context`, `transition_to`, or `skip_node` — those belong to the top-level supervisor, not node workers. The engine builds a `result_schema` from `node.writes` (plus a `_next_node` enum on multi-transition nodes) and constrains the child's output to match. See [Agent nodes](/processes/agent-nodes).
4. **Agent nodes get only the tools you declare in `node.tools`.**
   Skills (`learn_*`) are available so the agent can self-unlock capabilities.
5. **Agent nodes use `sys:ProcessAgentNode` by default.**
   It injects process orientation automatically. Override with `node.interaction` only when you need a custom interaction with its own input schema.
6. **Every node should have a `human_description`.**
   One or two sentences in plain language, distinct from the developer-facing `description`. Surfaces in observability. Undescribed nodes read as noise in the run report.
7. **Template references throw on missing fields.**
   Only reference context fields guaranteed to exist at the node.
8. **Every `condition` node must match a branch or have `default: true`.**
   No match and no default = runtime error.
9. **Context is capped at 64 KB serialized.**
   Store large payloads as artifact URIs and reference them.
10. **Do not overload collection fanout and fixed split/join in your own mental model.**
    Use `condition` to choose one path, `branch` to run a fixed set of named branches and join, and `foreach` to repeat a child body over a collection.
11. **Fanout child bodies do not own routing.**
    In `foreach` and `branch`, the child body should be `tool`, `interaction`, `agent`, or `process` only, with no nested `transitions` or `branches`.
12. **Any node that writes context must declare `writes`.**
    A non-empty `context_update`, human task answer, interaction result, agent result, collected `foreach` output, or collected `branch` output is rejected unless the parent node declares the target fields in `writes`.
13. **Supervisor overrides must be explicit.**
    `transition_to` follows declared exits by default, and `skip_node` requires `skippable: true`. Use `metadata.supervisor.allow_transition_override` or `metadata.supervisor.allow_skip` only for deliberate break-glass cases.
14. **Large processes need visual metadata.**
    Set `metadata.phase`, `metadata.lane`, and `metadata.order` on nodes when the graph has more than a straight-line handful of steps. The run UI uses those values for the Process Navigator and stable layout.
15. **`set_context` tool nodes use an `updates` envelope.**
    A process tool node that calls `set_context` must pass `{ "updates": { ... } }`, not raw context fields at the top level. The node still declares `writes` for every updated field.
16. **Use real interaction names.**
    For open-ended agent behavior, prefer `sys:GeneralAgent` or a custom agent interaction. Use `sys:ProcessAgentNode` for normal worker-agent nodes unless you have a specific interaction contract to override.

### Assignees

Human-task `assignee` is either a group reference (`group:`) or a concrete user id. `role:` is not supported — use `group:` instead. Leave unset if the task should be available to anyone who can see the inbox.

## Process board metadata

The process graph is readable only when authors describe the business structure, not just the transitions. For larger processes, model the visual board deliberately:

- **`metadata.phase`** is the stage of work: intake, AI review, approval, fulfillment, closeout.
- **`metadata.lane`** is the responsible actor or system: requester, AI assistant, policy, legal, procurement, outcome.
- **`metadata.order`** is the stable sort key inside that lane.
- **Transition and branch `label`** values are the business names of exits: approved, rejected, needs legal, amount >= 10000.

In the swimlane view, lanes render as vertical responsibility bands and nodes stack top-to-bottom within each lane. The main flow usually moves left-to-right across lanes; alternate paths can loop down or back across lanes. Keep lane names short and consistent.

Add layout metadata to every node in multi-stage processes:

```json
{
    "agent2_asset_generation": {
        "type": "agent",
        "title": "Agent 2 — Campaign Asset Generation",
        "metadata": {
            "phase": "Gate 2",
            "lane": "agent",
            "order": 10
        },
        "writes": ["asset_package"],
        "transitions": [
            {
                "to": "gate2_asset_review",
                "label": "Send package to review"
            }
        ]
    },
    "gate2_asset_review": {
        "type": "human_task",
        "title": "Gate 2 — Asset Package Review",
        "metadata": {
            "phase": "Gate 2",
            "lane": "review",
            "order": 20
        }
    }
}
```

Use a small, stable vocabulary for `lane`: business roles (`requester`, `legal`, `finance`, `claims_adjuster`) are better than implementation terms when humans need to read the board. Use implementation lanes (`ai_assistant`, `policy`, `tooling`) when ownership is genuinely a system.

Add `label` to transitions and branches when the guard is not obvious. The graph renders those labels on the edge, and the inspector shows the selected transition / branch as YAML.

### Improving layout with the assistant

When a graph becomes hard to read, use the Studio Assistant or the **Improve layout** action. It calls `LayoutProcessDefinition` with the current definition and optional notes about the current rendering. The interaction is constrained to visual fields:

- node `title`, `description`, and `human_description`
- node `metadata.phase`, `metadata.lane`, `metadata.order`, and optional `metadata.position`
- transition / branch `label`

It should not change runtime behavior: no node ids, node types, context schema, inputs, writes, tools, interactions, guards, targets, defaults, or tasks.

## Hand-authoring workflow

If you're editing directly in Studio or in a Git checkout:

1. Start from the schema: define `context.schema` first. Every field a node writes must exist here.
2. Lay out nodes and transitions. Name them for what they do, not how they work (e.g. `legal_review`, not `human_task_1`).
3. Fill each node's `human_description`. Ask yourself: what would a reviewer read here during observability?
4. For `agent` nodes, set the minimum `writes` scope — the result_schema is derived from it, so tighter writes means stricter agent output.
5. Add visual metadata and transition labels before the graph gets large. It is much easier to keep the run view readable while authoring than to repair it later.
6. Validate. Validation catches unreferenced nodes, branches with no default, and schema mismatches.
7. Save the draft. Run it against a known-good input. Iterate. Publish only when it is stable and the publishing user confirms.

## Versioning

Process definitions use draft/published versioning:

- New definitions are drafts at `version: 1`.
- Draft edits mutate the latest draft.
- Published revisions are immutable.
- Editing a published head creates the next draft head instead of changing the published version.
- Listings show only the latest/head revision by default; use the Versions tab or `all_versions` only when you need history.
- Publishing requires explicit confirmation and can include a comment.
- A historical version can be reverted into the current draft from the Versions tab.

Already-running processes continue to execute the definition that was passed into their Temporal workflow at start. Editing or reverting the catalog entry does not change an in-flight run.

## Next

- [Node types](/processes/node-types) — the reference for each `type`.
- [Observability](/processes/observability) — how to watch a run and debug what an agent node actually produced.

---

## The Process Model

Source: https://docs.vertesiahq.com/processes/model
Markdown: https://docs.vertesiahq.com/llms/processes/model.md

This page is about **how to think** about a process, not how to use the API. If you read only one page in this section before writing your first process, read this one.

## The thesis

A business process is a sequence of decisions and actions. Some of those decisions are deterministic ("if value > 50K, route to legal"). Some are probabilistic ("extract the parties from this contract"). Traditional tools force you to pick a side:

- A **workflow engine** forces deterministic code. LLM output is a string you parse at your own risk.
- A **chat agent** forces probabilistic reasoning. The same input gives different routes depending on mood.

The Vertesia process engine is designed to sit at the seam. The engine owns the deterministic parts — transitions, guards, routing, validation. The agents own the probabilistic parts — reading a document, judging risk, drafting output — *bounded by a schema the engine enforces*.

This is the whole point. Everything else — `result_schema`, `writes`, `_next_node`, guards, condition nodes — follows from this separation.

As the native format evolves, the control-flow vocabulary is settling into three distinct ideas:

- `condition` for choosing one path
- `branch` for fixed split/join fanout
- `foreach` for collection iteration

That split matters because BPMN structured parallelism maps to `branch`, while multi-instance fanout maps to `foreach`.

Persisted native definitions are also explicitly versioned with `format_version: 1`. That field is part of the process-definition contract, not incidental editor metadata.

## Three axes: control, state, behavior

A process definition cleanly separates three concerns:

| Concern | Where it lives | Who owns it |
| --- | --- | --- |
| **Control flow** | `transitions`, `branches`, `guards` | The engine (deterministic) |
| **State** | `context` (typed by `context.schema`) | The engine, mutated through `node.writes` |
| **Behavior** | Node bodies — prompts, tools, interactions, tasks | Agents, interactions, humans, or deterministic code |
| **Presentation** | `metadata.phase`, `metadata.lane`, `metadata.order`, transition labels | Authors, for observability |

A node never decides unilaterally. A tool node fills a fixed value. An interaction returns a schema-shaped object. An agent returns a schema-shaped object and optionally picks from declared transitions. A human task writes declared fields and enables guarded transitions. In every case, the engine validates, applies writes, evaluates guards, and picks the next node.

If you catch yourself writing a prompt that says "then decide whether to do X or Y" — stop. Lift that decision into a transition guard, and make the node return the signal the guard needs.

Presentation metadata is not part of runtime correctness. The workflow can execute without it. It matters because real business processes get wide quickly: approval gates, revision loops, escalation paths, and finalization branches become hard to read as a raw graph. Set `metadata.phase`, `metadata.lane`, and `metadata.order` so the run UI can group the process into navigable stages. Add transition or branch `label` values when a guard deserves a business name.

## Writes scope is a contract

`node.writes` isn't a nit; it's *the* contract between a node and the rest of the process. A node that emits a non-empty context update must declare the fields it intends to write. Missing `writes` is treated as "this node writes nothing," not "this node may write anything."

For an agent node, the engine:

1. Builds a `result_schema` from `node.writes` intersected with `context.schema.properties`.
2. Hands that schema to the child conversation so the LLM is physically constrained to emit those fields (and only those fields).
3. Validates the returned object against both the schema and the writes list before applying anything.

Consequences:

- **Agent impact is bounded.** A misbehaving agent can't write `total_value` on a node that only declares `legal_decision`. The engine refuses.
- **Routing is trustworthy.** Guards can key off `total_value` without worrying whether some earlier node silently clobbered it.
- **Debugging is crisp.** The per-node context diff in the run inspector shows exactly what each node changed. Nothing else.

Keep writes as tight as possible. Three named fields is better than a single blob.

## Why the engine owns routing

Agents are non-deterministic by design. For a chat, that's a feature. For a business process that decides whether a contract is reviewed by a human, it's a liability.

So routing lives in the definition:

- **`auto`** transitions with JSON Logic guards — the engine picks based on context.
- **`agent`** transitions with a `_next_node` enum in the result schema — the agent picks, but only from the declared set, and only with a value the schema accepts.
- **`user`** transitions — a human signal drives the move, through the Task Inbox or the Advance button.
- **`condition`** nodes — pure routing, no behavior, required `default: true` fallback.

What you *don't* do is let an agent call a `transition_to` tool mid-thought. That tool exists (see below) but only in supervised mode, and only for the top-level orchestrator.

## Durability and human time

Because a process runs as a Temporal workflow, every checkpoint is a resumable point. Concretely:

- An agent crash retries the node. The run doesn't restart.
- A human task can wait days. The workflow parks, the cluster can redeploy, and the signal still fires when the answer arrives.
- A worker redeploy mid-run resumes from the last checkpoint. Context is preserved.

This is load-bearing for real processes. Contract review, compliance checks, fund operations, content pipelines — all have steps that genuinely take time or block on people. The engine absorbs that naturally.

## Versioning

Process definitions are versioned. Each `create_process_definition` or edit bumps `version`. When a run starts, the engine snapshots the current definition into the run (`process_definition_snapshot`). The run walks that snapshot; editing the published definition afterward does **not** affect in-flight runs.

This means publishing a revision is safe — no lurking state about which runs will see the new version. New starts see new, old continues old.

## Two execution modes

Every run has a `run_type`: **programmatic** or **supervised**. These are the two ways a process can be driven.

### Programmatic (default)

The engine walks the definition node by node, applying writes and picking transitions exactly as above. No outer LLM is in the loop. This is what every run starts as unless you explicitly pick supervised.

Use programmatic for the 95% of cases where the process flow *is* the logic. Predictable, auditable, cheap.

### Supervised

Supervised runs add a top-level `ProcessSupervisor`: a long-lived child conversation workflow that starts with the process and receives structured process events as the run advances. It sees the current node, recent history, available transitions / branches, current context, and any failure metadata.

The supervisor can respond with commands:

- **`continue_process`** — let the deterministic process keep going.
- **`set_context`** — propose a context repair. The process validates the merged context against the process schema and blocks all `_` fields.
- **`transition_to`** — move the process through a declared exit from the current node.
- **`skip_node`** — treat the current node as skipped and move forward when the node is explicitly skippable.
- **`retry_node`** — re-enter the current or requested node.
- **`fail_process`** — fail the process with a supervisor-provided reason.

The supervisor has process-control tools the worker agents never get: `set_context`, `transition_to`, `skip_node`, `continue_process`, `retry_node`, and `fail_process`. The workflow converts those tool calls into commands and applies them only after validation.

The orchestrator is given the same observability a human would have and can steer when things go sideways. This is the path for:

- Running a process with a human-in-the-loop chat — "review this step before continuing."
- Handling ambiguous inputs where the deterministic flow isn't enough.
- Meta-reasoning over many process instances (e.g. batch runs where the orchestrator decides priority).

The supervised orchestrator is effectively an overlay. The engine still owns state mutation and routing — `set_context` must satisfy the process context schema, internal `_` fields cannot be written, and `transition_to` must follow a declared transition or branch from the current node. `skip_node` is accepted only when the current node has `skippable: true`.

If you need break-glass behavior, opt in explicitly with supervisor policy metadata:

```json
{
    "metadata": {
        "supervisor": {
            "allow_transition_override": true,
            "allow_skip": true
        }
    }
}
```

The same `metadata.supervisor` policy can also be placed on an individual node. Node-level policy is preferable when only one step needs a controlled override.

A run's `config.user_message` carries an optional message from the user to the orchestrator — "prioritize speed over thoroughness on this batch."

The worker agents inside nodes are **always** in programmatic posture: constrained by result_schema, bounded by writes. They don't know whether they're running under a supervised or programmatic outer loop. That invariant is intentional — agents at the edge stay simple and auditable regardless of what's driving the overall run.

## When not to reach for a process

A process is the wrong tool when:

- The whole task is a single LLM call. Use an interaction.
- The flow is straight-line and needs no state. Use a workflow.
- The user wants an open-ended chat and there's no fixed sequence. Use an agent.
- The sequence is long but purely deterministic (ETL, file processing). Use a workflow.

Reach for a process when you have **branching**, **state that accumulates**, **human gates**, and at least one step where agentic reasoning pays off. That's the sweet spot.

## Summary — how to think about it

1. **Separate**: control flow, state, behavior. The definition encodes all three explicitly.
2. **Bound agents with writes + result_schema.** The engine only accepts what the schema allows.
3. **Route deterministically.** Guards on context, never on prose.
4. **Treat durability as free.** Let processes wait for humans, retry on failures, span days.
5. **Version confidently.** Snapshots mean publishing is safe.
6. **Pick the mode.** Programmatic by default, supervised when you want an LLM driving the outer loop.

The rest of this section — [node types](/processes/node-types), [agent nodes](/processes/agent-nodes), [authoring](/processes/authoring), [observability](/processes/observability), [task inbox](/processes/task-inbox), [tutorial](/processes/tutorial-contract-review) — is mechanics. Come back to this page when something feels off; usually what's off is one of the principles above.

---

## Node types

Source: https://docs.vertesiahq.com/processes/node-types
Markdown: https://docs.vertesiahq.com/llms/processes/node-types.md

Each node in a process definition has a `type`. The engine's behavior for executing the node, applying writes, and picking the next transition depends on the type.

Every node also accepts two documentation fields:

- **`description`** — developer-facing notes for authors of the definition.
- **`human_description`** — a one- or two-sentence plain-language explanation shown to users watching the run. Fill this in — undescribed nodes read badly in observability.

## `tool`

A deterministic step. Typically used to apply a fixed `context_update` (e.g. setting `legal_decision: "auto_approved"` when bypassing human review) or to invoke a pre-registered tool.

```json
"auto_approve": {
    "type": "tool",
    "config": {
        "context_update": { "legal_decision": "auto_approved" }
    },
    "writes": ["legal_decision"],
    "transitions": [ { "to": "store_output" } ]
}
```

- **Writes**: either the literal `config.context_update`, or derived from `node.writes` if set. Any non-empty context update requires `node.writes`.
- **Transitions**: `auto`-triggered by default; guards supported.

## `interaction`

Invokes a Vertesia interaction by name. Use for deterministic single-shot LLM calls (summarization, extraction, classification) where the interaction already has a prompt and is registered in the project.

```json
"classify_invoice": {
    "type": "interaction",
    "interaction": "ClassifyInvoice",
    "input": { "doc_id": "{{invoice_doc_id}}" },
    "writes": ["gl_code", "amount"],
    "transitions": [ { "to": "approve" } ]
}
```

The engine forwards a derived **`result_schema`** built from `node.writes` ∩ `process.context.schema.properties` to the interaction execution, so the returned JSON lines up with the node's writes contract. The interaction's own schema is overridden for the duration of the call.

- **Writes**: declared in `node.writes`; populated from the interaction's structured JSON output.
- **Transitions**: `auto`-triggered by default.

## `agent`

A worker agent that may use tools. The engine builds a `result_schema` from `node.writes` (plus a `_next_node` enum if the node declares more than one agent-triggered transition) and asks the child conversation to return exactly that JSON. The agent can use any tool declared in `node.tools`; `learn_*` skills remain available.

```json
"extract_terms": {
    "type": "agent",
    "prompt": "Extract key terms from the contract at {{contract_doc_id}}.",
    "tools": ["fetch_document"],
    "writes": ["parties", "term_length", "governing_law", "total_value", "auto_renewal"],
    "transitions": [ { "to": "flag_clauses", "trigger": "agent" } ],
    "human_description": "Reads the contract and extracts key terms into process context."
}
```

See [Agent nodes](/processes/agent-nodes) for the full contract. Key rules:

- Agent nodes do **not** get process-control tools such as `set_context`, `transition_to`, `skip_node`, `continue_process`, `retry_node`, or `fail_process`. Those are top-level orchestrator tools; worker agents produce structured output instead.
- If a node declares writes but the child conversation returns no parseable schema-matching output, the engine fails the node rather than silently advancing.

## `process`

Starts another process workflow and waits for it to complete. Use this for reusable subprocesses such as invoice review, document intake, or vendor onboarding. The child run gets its own process history, artifacts, gates, and human tasks.

```json
"review_invoice": {
    "type": "process",
    "process": "65f000000000000000000000",
    "input": {
        "invoice": "{{invoice}}"
    },
    "returns": {
        "from": "context.decision"
    },
    "writes": ["invoice_decision"],
    "transitions": [ { "to": "route_invoice" } ]
}
```

Inline definitions are also supported:

```json
"review_invoice": {
    "type": "process",
    "process_definition": {
        "format_version": 1,
        "process": "invoice_review",
        "initial": "extract",
        "context": {
            "schema": { "type": "object", "additionalProperties": true },
            "initial": {}
        },
        "nodes": {
            "extract": { "type": "agent", "writes": ["decision"], "transitions": [ { "to": "done" } ] },
            "done": { "type": "final" }
        }
    },
    "input": { "invoice": "{{invoice}}" },
    "writes": ["invoice_review"]
}
```

- **`process`** — stored process definition id, built-in `sys:*` process, or installed app process such as `app:contracts:contract_review`.
- **`process_version`** — optional published version to pin when `process` references a stored process history. Omit to use the latest head.
- **`process_definition`** — inline `ProcessDefinitionBody`; useful for local reusable building blocks. Pair with `process: "tmp:"` only when you need a temporary id for the inline definition.
- **`run_type`** — `programmatic` by default; `supervised` is allowed for child processes that need an orchestrator mode.
- **`input`** — merged over the child process definition's initial context.
- **`returns.from`** — path to read from the completed child state, commonly `context.some_field`.
- **`returns.context`** — list of child context fields to return when a single `from` path is not enough.
- **Writes**: declared in `node.writes`; populated from the selected child output.

## `human_task`

Pauses the process until a person submits an answer via the Task Inbox. The `task` definition describes what the reviewer sees.

```json
"legal_review": {
    "type": "human_task",
    "task": {
        "title": "Legal Review Required: {{parties}}",
        "description": "Please review and submit your decision.",
        "assignee": "group:legal",
        "fields": [
            { "name": "legal_decision", "type": "select", "required": true, "options": ["approve", "reject", "request_edits"] },
            { "name": "legal_notes", "type": "text", "required": false }
        ]
    },
    "writes": ["legal_decision", "legal_notes"],
    "transitions": [
        { "to": "store_output",   "guard": { "==": [{ "var": "legal_decision" }, "approve"] } },
        { "to": "rejected",        "guard": { "==": [{ "var": "legal_decision" }, "reject"] } },
        { "to": "flag_clauses",    "guard": { "==": [{ "var": "legal_decision" }, "request_edits"] } }
    ]
}
```

Details:

- `task.title` and `task.description` support `{{var}}` placeholders that are expanded against the process context at task creation time and stored expanded on the task. A new task (new context) will render fresh values.
- `assignee` must be either `group:` or a concrete user id. `role:` is not supported.
- When the task is submitted, each answer field is copied into context following `node.writes`; then the guarded transitions pick the next node.

## `condition`

Pure routing. Evaluates `branches` in order, picks the first whose `when` rule passes, or falls through to the branch marked `default: true`. Every condition node **must** declare either a matching `when` for all possible states or a `default` branch, otherwise runtime fails.

```json
"risk_route": {
    "type": "condition",
    "branches": [
        { "to": "legal_review",
          "when": {
              "or": [
                  { ">": [{ "var": "total_value" }, 50000] },
                  { "==": [{ "var": "has_critical_flag" }, true] }
              ]
          }
        },
        { "to": "auto_approve", "default": true }
    ]
}
```

Guards and branch rules use [JSON Logic](https://jsonlogic.com/) with a Vertesia-specific extension: `artifact_exists: { var: "some_path" }` resolves to `true` if an artifact with that path exists on the run.

## `foreach`

Runs a child node body once per item in a context array. The child can be a `tool`, `interaction`, `agent`, or `process` node.

```json
"process_each_line": {
    "type": "foreach",
    "foreach": "invoice_lines",
    "as": "line",
    "node": {
        "type": "interaction",
        "interaction": "ClassifyLine",
        "input": { "line": "{{line}}" },
        "writes": ["gl_code", "amount"]
    },
    "collect": "classified_lines",
    "failure_policy": "collect_errors",
    "writes": ["classified_lines"]
}
```

- **`foreach`** — context path to an array (max 1000 items).
- **`as`** — the variable name each item is exposed as inside the child node's context.
- **`item_id`** — optional template for a stable per-item id, e.g. `{{invoice.id}}`.
- **`max_concurrency`** — optional positive integer cap. If unset, all items are launched together.
- **`collect`** — context key to accumulate per-item results into.
- **`failure_policy`** — `fail_fast` (default) or `collect_errors` to keep the survivors and return error info for the failures.

For subprocess fanout, use `node.type: "process"`:

```json
"review_invoices": {
    "type": "foreach",
    "foreach": "invoices",
    "as": "invoice",
    "item_id": "{{invoice.id}}",
    "max_concurrency": 25,
    "node": {
        "type": "process",
        "process": "65f000000000000000000000",
        "input": { "invoice": "{{invoice}}" },
        "returns": { "from": "context.decision" }
    },
    "collect": {
        "into": "invoice_results",
        "include": ["status", "index", "item_id", "output", "error", "child_run_id"]
    },
    "failure_policy": "collect_errors",
    "writes": ["invoice_results"]
}
```

String `collect` keeps the compact form. Object `collect` returns an envelope for each item. The common fields are `status`, `index`, `item_id`, `item`, `output`, `context_update`, `error`, `child_run_id`, `child_workflow_id`, and `child_workflow_run_id`.

The nested `node` is task-like leaf work only: use `tool`, `interaction`, `agent`, or `process`, and keep routing on the parent `foreach` node.

## `branch`

Use `branch` for structured split/join semantics:

- a fixed named set of branches
- each branch usually modeled as a child subprocess or inline child process definition
- the engine launches all branches
- the parent waits for all of them, then continues

That is the clean mapping for BPMN structured parallel split/join. It is intentionally different from collection fanout:

- `foreach` means "run the same child body once per item"
- `branch` means "run these named branches and join"

Each branch child is also task-like leaf work only: use `tool`, `interaction`, `agent`, or `process`, and keep routing on the parent `branch` node.

```json
"review_tracks": {
    "type": "branch",
    "join": "all",
    "branches": [
        {
            "id": "legal",
            "title": "Legal Review",
            "node": {
                "type": "process",
                "process": "legal_review_subprocess"
            }
        },
        {
            "id": "finance",
            "title": "Finance Review",
            "node": {
                "type": "process",
                "process": "finance_review_subprocess"
            }
        }
    ],
    "collect": {
        "into": "review_results",
        "include": ["status", "branch_id", "branch_title", "output", "error"]
    },
    "writes": ["review_results"],
    "transitions": [{ "to": "consolidate" }]
}
```

## `final`

Terminal state. Entering a `final` node completes the process run.

```json
"done":      { "type": "final" },
"rejected":  { "type": "final" }
```

Use multiple final nodes when you want the run's final status to carry meaning (e.g. `done` vs `rejected`), which is visible in the run graph.

---

## Observability

Source: https://docs.vertesiahq.com/processes/observability
Markdown: https://docs.vertesiahq.com/llms/processes/observability.md

A process run is durable. Everything that happened — which node executed, what the context was before and after, what the child agent said, which tools it called — is captured. The run UI surfaces this through three tabs.

## Process tab

The default view. Two panes and a strip:

### State chart

An interactive graph of the process definition. The renderer uses the visual metadata in the definition when present:

- `node.metadata.phase` groups a large process into major stages.
- `node.metadata.lane` separates responsibility, such as `agent`, `review`, `routing`, or `finalization`.
- `node.metadata.order` gives a stable order inside a phase / lane.

If that metadata is absent, the UI infers a readable grouping from node titles and ids. That works for quick drafts, but published definitions should set metadata explicitly so the graph stays stable as the process evolves.

The graph has two complementary renderers:

- **Diagram** — a compact node-link view for quick topology checks.
- **Swimlanes** — a process board that groups nodes by responsibility lane and phase. Use this for business review, demos, and long-running processes with human handoffs.

The left **Process Navigator** groups nodes by phase and lets you jump without panning across the full canvas. The graph can render in three visibility modes:

- **Main** — the primary path through the process, hiding most loop-limit and escalation detours.
- **Nearby** — the selected node, its direct incoming / outgoing neighbors, and their guards.
- **All** — the complete process definition.

Each node card shows:

- Node id (or title, if set).
- Node type (`agent`, `human_task`, …) as a pill.
- `human_description` if set, otherwise `node.title`.
- `tool:` or `interaction:` ref if declared.
- The writes scope as small chips.

Color rings indicate state: the current node has an info-colored ring, completed nodes a success ring, failed nodes a destructive border, skipped nodes are dimmed. The initial node is always tagged.

On a run, the executed route is highlighted separately from the untaken graph. Completed nodes show success state, the current node is emphasized, and the transitions that were actually taken use a stronger primary stroke. This matters on branching processes: the definition may have many exits, but the run should make the chosen path obvious.

Transitions and condition branches are selectable too. Guarded exits render as small edge labels. Click a guard label or edge to inspect the YAML definition on the right.

### Node inspector

Click a node to inspect it on the right pane:

- **Header** — the node's `human_description` in plain text, plus badges and a Retry action (for admins, when the run is active and this is the current node).
- **Transitions** — the declared targets with labels / triggers, and for the currently-active node an **Advance** button that emits a manual transition signal (admin-gated).
- **Tasks** — any human tasks created for this node, embedded with the response form so admins can answer inline.
- **Node History** — every time the node has been entered, with:
    - Status (`completed`, `running`, `skipped`, `failed`)
    - Entered / exited timestamps
    - The **context diff** that was applied on exit, rendered as a scrollable JSON block
- **Definition** — `type`, `tool`, `interaction`, `writes`, `skippable`.
- **Context** — the full current process context, scrollable.
- **YAML** — the selected node, transition, branch, task, input, metadata, or runtime state as copyable YAML.
- **Explain** — calls the built-in `sys:ExplainProcess` interaction with the process definition, current context, selected node, and selected guard. Use it to get a concise explanation of what the step does, why it routes the way it does, and what can go wrong.

The context diff is what most debugging starts with: for each run of the node, it shows exactly what was written. An empty diff after an agent node running a schema-constrained write usually means the child conversation produced unparseable output (the run will have failed).

When a transition is selected, the inspector switches from node details to edge details. This is the fastest way to debug "why did it go there?" questions: inspect the guard YAML, current context, and explanation together.

### Timeline strip

Along the bottom: one pill per `node_history` entry in chronological order. Clicking a pill selects that node in the inspector.

## Conversation tab

Mounts the same `ModernAgentConversation` you get for standalone agent runs, scoped to the process run id. Every agent node's child conversation lives under that run as its own **workstream** (named after the node id). The conversation view's workstream tabs let you flip between:

- the **main** workstream (usually read-only for programmatic runs — the process workflow doesn't do LLM calls itself),
- and one workstream per agent node that ran.

Each workstream streams live while the node is active, and stays inspectable after completion — you can read the full message history, see every tool call (with inputs and outputs), and copy structured outputs.

This is the equivalent of the agent observability view you already use for conversational agents, but rooted at the process run rather than a bare agent run.

For **supervised** process runs, the Conversation tab also exposes chat input to the top-level `ProcessSupervisor`. The process workflow posts structured events into that supervisor conversation: run start, node entered, node completed, guard failed, no transition matched, node failed, and human task waiting. The supervisor can answer by calling process-control tools: `set_context`, `transition_to`, `skip_node`, `continue_process`, `retry_node`, or `fail_process`. The process workflow remains the source of truth: it validates context writes, declared transition targets, skip policy, internal `_` fields, and checkpoint sequence before applying anything.

The supervisor is useful when the deterministic engine reaches an ambiguous point: a guard fails, no transition matches, a node result needs interpretation, or a human wants a controlled override. In normal programmatic runs there is no top-level supervisor conversation; only child workstreams for agent nodes appear.

## Observability tab

The generic **Agent Observability** view, scoped to the process run id. Useful for:

- Temporal history (every signal, every activity, every retry).
- Conversation snapshots per activity (before / after, downloadable).
- Tool-call metrics and context-window charts.

Because the process run id is also the agent run id for every child conversation, this tab surfaces the full agent hierarchy: the outer process as the parent, each agent node as a sub-agent.

## Task inbox

Not a tab of the run view, but related: the **Task Inbox** in the global nav shows process `human_task` tasks and agent `ask_user` tasks that the current user can read. See [Task Inbox](/processes/task-inbox).

## Typical debugging flow

1. Open the failed run; the State tab usually points at the node that failed.
2. In the Inspector, read the last node-history entry's `context_diff`. Empty diff on an agent node → schema didn't match, jump to the Conversation tab for that workstream.
3. In the Conversation tab, switch to the node's workstream, read the final assistant message. If it's prose, the agent didn't emit JSON. If it's a partial JSON, you can see exactly which required field was missing.
4. If the problem is routing, click the edge or guard label and inspect the guard YAML against the current context.
5. Use **Explain** on the selected node or guard when you need a plain-language summary before changing the definition.
6. Fix `node.writes`, the guard, or the prompt, save a new version, and start a fresh run.

---

## Processes

Source: https://docs.vertesiahq.com/processes/overview
Markdown: https://docs.vertesiahq.com/llms/processes/overview.md

The Vertesia **Process Engine** runs business processes that are a hybrid of deterministic state machines and agentic reasoning. A process defines a graph of **nodes** connected by **transitions**, walked by a Temporal workflow. Some nodes are pure code (conditions, tool calls), some call an interaction, some delegate to an autonomous agent, and some pause for a human to review.

The engine is the answer when a plain conversational agent is too loose and a plain DSL workflow is too rigid. You get deterministic routing, durable state, and human-in-the-loop gates — plus agents that can do open-ended sub-tasks bounded by a schema.

## When to use a process vs a workflow or an agent

| You want… | Use |
| --- | --- |
| A single agent chatting and calling tools | [Agent](/agent/overview) |
| Deterministic pipelines authored in JSON DSL or TypeScript | [Workflows](/workflows/overview) |
| A multi-step business process with branching, gates, retries, and mixed automation + humans | **Processes** (this section) |

A process is well-suited when you can describe the flow as "first do X, then either Y or Z depending on the result, then a human reviews, then finalize."

One authoring distinction matters in the BPMN-aligned native model:

- **`condition`** means choose one path
- **`branch`** means run a fixed set of named branches and join
- **`foreach`** means repeat one child body over a collection

For `foreach` and `branch`, the nested child body is task-like leaf work only: use `tool`, `interaction`, `agent`, or `process`, and keep routing on the parent node.

Persisted native definitions also carry `format_version: 1` explicitly so future migrations have a stable schema boundary.

## Core concepts

### Process definition

A JSON document that the author commits. Key fields:

- **`process`** — stable identifier (e.g. `contract_review`).
- **`initial`** — the first node id to enter.
- **`context.schema`** — a JSON Schema describing every field the process may read or write. Acts as the typed state the process accumulates.
- **`context.initial`** — starting values for that context.
- **`nodes`** — a map of `nodeId → NodeDefinition`, each with a `type` and optional `transitions`.

### Context

Every running process carries a **context** object, typed by `context.schema`. Any node that writes process state must declare **`writes`** — the exact fields it is allowed to update. The engine rejects non-empty context updates when `writes` is missing and rejects writes outside that list. Context is capped at 64&nbsp;KB serialized — for large payloads, store artifact URIs.

### Transitions

Each node can declare `transitions`, each with:

- **`to`** — target node id
- **`trigger`** — `"auto"` (engine picks after guards), `"agent"` (chosen by an agent node's structured output), or `"user"` (driven by a human signal)
- **`guard`** — optional JSON Logic rule evaluated against context

Condition nodes use `branches` instead of transitions and route by `when` rules with a required `default: true` branch.

### Node definition (high level)

```json
{
    "type": "agent",
    "prompt": "Extract the key terms…",
    "tools": ["fetch_document"],
    "writes": ["parties", "term_length", "total_value"],
    "transitions": [
        { "to": "flag_clauses", "trigger": "agent" }
    ],
    "human_description": "Reads the contract and writes the extracted key terms into process context."
}
```

See [Node types](/processes/node-types) for the full reference.

## Runtime model

Each process run is a Temporal workflow (`ExecuteProcessWorkflow`) that:

1. Loads the resolved definition into the workflow input (processes are versioned; runs execute against the definition they started with).
2. Walks node by node, checkpointing to MongoDB every step.
3. For **agent** and **interaction** nodes, spawns a child conversation workflow scoped to a per-node workstream — so every node gets its own conversation view, its own tool use, and its own artifact namespace.
4. Applies writes to context through the validator (schema + writes scope), then picks the next transition.
5. On a **human_task** node, creates a task and waits for a signal from the Task Inbox.

Because it's a Temporal workflow, any node failure is durable and retryable; the run can be resumed after restarts.

Structured split/join follows the same Temporal-native pattern: start child branches, wait for all of them, then continue. Collection fanout is the separate "repeat over items" case.

## Observability

Each run has a dedicated UI with three tabs:

- **Process** — the state chart, per-node inspector (writes, transitions, context diff per entry), timeline, tasks.
- **Conversation** — the live streaming conversation for whichever node's child workflow is active, with workstream tabs for each node's agent run.
- **Observability** — the generic agent observability view (Temporal history, conversation snapshots, tool metrics) scoped to the process run.

For end users who need to act on a process, the **Task Inbox** surfaces readable `human_task` nodes assigned to them, their groups, or available to claim.

## Two execution modes

Every run has a `run_type`:

- **`programmatic`** (default) — the engine walks the definition deterministically. No outer LLM. This is the right choice for the vast majority of production processes.
- **`supervised`** — a top-level `ProcessSupervisor` conversation watches the run and can request `set_context`, `transition_to`, `skip_node`, `retry_node`, `continue_process`, or `fail_process` commands. The process workflow validates and applies those commands. `transition_to` must target a declared exit unless an explicit supervisor override policy is set; `skip_node` requires `skippable: true` or an explicit skip policy. Worker agents inside nodes remain schema-constrained regardless.

The conceptual picture of why both modes exist and how they relate is in [The Process Model](/processes/model).

## Next

- [The Process Model](/processes/model) — how to think about processes (read this before authoring).
- [Node types](/processes/node-types) — the reference for each `type`.
- [Agent nodes](/processes/agent-nodes) — how a worker agent is constrained by a result schema and the node's declared tools.

---

## Task Inbox

Source: https://docs.vertesiahq.com/processes/task-inbox
Markdown: https://docs.vertesiahq.com/llms/processes/task-inbox.md

Whenever a process hits a `human_task` node, the engine creates a task and waits for an answer. Agents can also create tasks through `ask_user`, which lets a user answer later from the same inbox. Users see those tasks in the **Task Inbox** (`/store/tasks`), a split-pane view with a filterable list on the left and the selected task's detail + response form on the right.

## What shows up in my inbox

The inbox filters to tasks that are actually mine to act on. Concretely, a task appears in the UI if:

- **it's unassigned** (available to anyone who can see the inbox), or
- **`task.assignee` is my user id**, or
- **`task.assignee` starts with `group:`** and I'm a member of that group.

Tasks assigned to other users or other groups are hidden. The filter runs client-side against the returned task page because the backend `list` endpoint only supports exact-assignee matching. Users with `task:read` or `task:manage` can read broader task lists; users without those permissions only get tasks the API allows them to read.

## Assignees

Every `human_task` node's `task.assignee` is one of:

- `group:` — owned by a group (most common for shared queues like `group:legal`).
- `<user_id>` — owned by one specific user.
- unset — up for grabs.

`role:` is **not** supported. Use `group:` for role-like semantics.

## Taking a task

When a task is addressed to a group you're in — or unassigned — the detail pane shows a **Take Task** button. Clicking it sets `assignee` to your user id, pinning the task to you so others know it's being worked. After claiming, the button falls away and the task shows your name as the assignee.

Note: in the current implementation, claiming a task calls the generic `PUT /api/v1/tasks/:taskId` endpoint which still requires `task:manage` permission. A dedicated self-claim endpoint that allows any eligible viewer to claim is planned; until then, claiming falls through the admin permission check.

## Filters

Along the top of the list:

- **Open** (default) — pending + in progress, the ones that need attention.
- **Pending** — not yet started.
- **In progress** — someone's working on it.
- **Completed** — already answered.
- **Cancelled** — tasks that were cancelled before completion.
- **All** — everything returned by the API for the current user.

## Submitting an answer

The detail pane renders `task.fields` as a form. Each field's type maps to an input:

- `select` → a dropdown with `options`.
- `text` → a textarea.
- `boolean` → a checkbox.
- `number`, `string` → the corresponding input.

Submitting a process task sends the answer back to the process workflow as a signal. The process resumes, applies writes per `node.writes`, and evaluates guards on the declared transitions (e.g. route to `store_output` when `legal_decision === "approve"`).

Submitting an agent `ask_user` task signals the agent conversation with the answer and resolves the pending ask so the agent can continue.

The important operational rule is that process-sourced tasks must resume the workflow, not only mark the task document as completed. Use the Task Inbox or the process run's **answer task** API path so the workflow receives the signal and the state chart advances.

## Opening the parent run

**Open Run** in the detail pane jumps to the full process-run observability view so you can see the state chart, read the context, and inspect earlier agent conversations before answering. This is often worth doing for anything more nuanced than a single approve/reject.

---

## Tutorial: Contract Review

Source: https://docs.vertesiahq.com/processes/tutorial-contract-review
Markdown: https://docs.vertesiahq.com/llms/processes/tutorial-contract-review.md

This tutorial builds a realistic process: **contract review**. It accepts a contract document, has an agent extract key terms, has another agent flag risky clauses, routes high-value or high-risk contracts to a legal reviewer via a human task, auto-approves the rest, and finally writes the result back to the store.

By the end, you'll have seen every node type working together and picked up the patterns that carry to other processes.

## What we're building

```
start
  └── extract_terms  (agent)
        └── flag_clauses  (agent)
              └── risk_route  (condition)
                    ├─ legal_review  (human_task)
                    │    ├─ store_output  (agent)   ← approve
                    │    ├─ rejected  (final)        ← reject
                    │    └─ flag_clauses  (loop back) ← request_edits
                    └─ auto_approve  (tool)
                         └── store_output  (agent)
                               └── done  (final)
```

Context fields (the state the process accumulates):

- `contract_doc_id` — input document reference.
- `parties`, `term_length`, `governing_law`, `total_value`, `auto_renewal` — written by `extract_terms`.
- `flagged_clauses`, `has_critical_flag` — written by `flag_clauses`.
- `legal_decision`, `legal_notes` — written by `legal_review`.
- `output_doc_id` — written by `store_output`.

## 1. Start from the schema

Every writable field must appear in `context.schema`. Start here.

```json
"context": {
    "schema": {
        "type": "object",
        "properties": {
            "contract_doc_id": {
                "type": "string",
                "format": "document",
                "editor": "document",
                "description": "Contract document to review."
            },
            "parties": { "type": "string" },
            "term_length": { "type": "string" },
            "governing_law": { "type": "string" },
            "total_value": { "type": "number", "description": "Total contract value in USD." },
            "auto_renewal": { "type": "boolean" },
            "flagged_clauses": {
                "type": "array",
                "items": {
                    "type": "object",
                    "properties": {
                        "clause_type": { "type": "string" },
                        "excerpt": { "type": "string" },
                        "risk_level": { "type": "string" },
                        "reason": { "type": "string" }
                    }
                }
            },
            "has_critical_flag": { "type": "boolean" },
            "legal_decision": { "type": "string" },
            "legal_notes": { "type": "string" },
            "output_doc_id": { "type": "string" }
        },
        "required": ["contract_doc_id"]
    },
    "initial": {
        "has_critical_flag": false,
        "auto_renewal": false,
        "flagged_clauses": []
    }
}
```

Two things to call out:

- **`contract_doc_id`** uses `format: "document"` **and** `editor: "document"`. Both are required: the first enables validation, the second makes the Start Run modal render a document picker instead of a raw text field.
- **`initial`** seeds a few fields so downstream guards don't have to reason about "unset vs false".

## 2. `extract_terms` — the first agent node

```json
"extract_terms": {
    "type": "agent",
    "human_description": "Reads the contract document and extracts the key terms (parties, term, law, value, auto-renewal) into process context.",
    "prompt": "You are a contract analyst. Extract the key terms from the contract at {{contract_doc_id}}. Only extract what is explicitly stated; if unknown use 0 for numeric fields and false for booleans.",
    "tools": ["fetch_document"],
    "writes": ["parties", "term_length", "governing_law", "total_value", "auto_renewal"],
    "transitions": [
        { "to": "flag_clauses", "trigger": "agent" }
    ]
}
```

What's happening:

- The engine builds a `result_schema` from `node.writes` ∩ `context.schema.properties`. Since there's exactly one agent-triggered transition, no `_next_node` is needed — the engine auto-advances to `flag_clauses`.
- The agent gets the `fetch_document` tool so it can actually read the contract. Skills (`learn_*`) remain available.
- The `{{contract_doc_id}}` placeholder is expanded against context before dispatch.

The agent's final response must be JSON matching the schema — something like:

```json
{ "parties": "ACCOR SA and Vertesia SAS", "term_length": "12 months", "governing_law": "French law", "total_value": 97500, "auto_renewal": false }
```

## 3. `flag_clauses` — another agent, same pattern

```json
"flag_clauses": {
    "type": "agent",
    "human_description": "Identifies risky clauses in the contract across four categories and flags critical risks.",
    "prompt": "You are a legal risk analyst. Fetch the contract at {{contract_doc_id}} and identify risky clauses in these categories: indemnification, unlimited liability, non-standard termination, IP assignment. For each flagged clause return an object with clause_type, excerpt, risk_level (low|medium|critical), and reason. Set has_critical_flag true if any clause is critical.",
    "tools": ["fetch_document", "search_documents"],
    "writes": ["flagged_clauses", "has_critical_flag"],
    "transitions": [
        { "to": "risk_route", "trigger": "agent" }
    ]
}
```

## 4. `risk_route` — a condition node

Route high-value or high-risk contracts to a human; everything else auto-approves.

```json
"risk_route": {
    "type": "condition",
    "human_description": "Routes contracts above $50K or with critical risk flags to legal review; everything else auto-approves.",
    "branches": [
        {
            "to": "legal_review",
            "when": {
                "or": [
                    { ">": [{ "var": "total_value" }, 50000] },
                    { "==": [{ "var": "has_critical_flag" }, true] }
                ]
            }
        },
        { "to": "auto_approve", "default": true }
    ]
}
```

Condition nodes evaluate `branches` top-to-bottom and require a `default: true` branch as a safety net.

## 5. `legal_review` — a human task

```json
"legal_review": {
    "type": "human_task",
    "human_description": "Pauses the process for a legal reviewer to approve, reject, or request edits.",
    "writes": ["legal_decision", "legal_notes"],
    "task": {
        "title": "Legal Review Required: {{parties}}",
        "description": "A contract requires legal review. Extracted terms and flagged clauses are available in the process context. Please review and submit your decision.",
        "assignee": "group:legal",
        "fields": [
            { "name": "legal_decision", "type": "select", "required": true, "label": "Decision", "options": ["approve", "reject", "request_edits"] },
            { "name": "legal_notes", "type": "text", "required": false, "label": "Notes or Edit Instructions" }
        ]
    },
    "transitions": [
        { "to": "store_output", "guard": { "==": [{ "var": "legal_decision" }, "approve"] } },
        { "to": "rejected",     "guard": { "==": [{ "var": "legal_decision" }, "reject"] } },
        { "to": "flag_clauses", "guard": { "==": [{ "var": "legal_decision" }, "request_edits"] } }
    ]
}
```

Highlights:

- The `title` uses `{{parties}}` — expanded at task creation time and stored expanded on the task (so reviewers see "Legal Review Required: ACCOR SA and Vertesia SAS"). Re-runs that revisit this node produce a new task with fresh values.
- The `assignee` is `group:legal`. Anyone in the `legal` group will see this in their Task Inbox. Users can **Take Task** to claim it to themselves.
- The three guarded transitions model a standard review outcome. `request_edits` loops back to `flag_clauses` so the agent can re-analyze with the legal reviewer's notes in context.

## 6. `auto_approve` — a tool node

A trivial deterministic step: just writes a fixed value into context and moves on.

```json
"auto_approve": {
    "type": "tool",
    "human_description": "Marks the contract as auto-approved when it clears the risk route without needing human review.",
    "config": {
        "context_update": { "legal_decision": "auto_approved" }
    },
    "writes": ["legal_decision"],
    "transitions": [ { "to": "store_output" } ]
}
```

## 7. `store_output` — a final agent node

Writes a markdown summary back to the content store.

```json
"store_output": {
    "type": "agent",
    "human_description": "Produces a markdown summary of the review and stores it as a new document.",
    "prompt": "Create a markdown artifact named contract-review.md containing the contract review results, then persist it with create_document using source: \"artifact:files/contract-review.md\". Use this markdown: \n\n# Contract Review\n\n## Decision\n{{legal_decision}}\n\n## Extracted Terms\n- Parties: {{parties}}\n- Term: {{term_length}}\n- Governing Law: {{governing_law}}\n- Total Value: ${{total_value}}\n- Auto-Renewal: {{auto_renewal}}\n\n## Flagged Clauses\n{{flagged_clauses}}\n\n## Legal Notes\n{{legal_notes}}\n\nReturn the new document id as output_doc_id.",
    "tools": ["write_artifact", "create_document"],
    "writes": ["output_doc_id"],
    "transitions": [
        { "to": "done", "trigger": "agent" }
    ]
}
```

## 8. Terminals

```json
"rejected": { "type": "final", "human_description": "The contract was rejected during legal review." },
"done":      { "type": "final", "human_description": "The contract has been reviewed and the output document stored." }
```

Multiple `final` nodes are fine — and useful — because the run's ending node tells a human reader how it ended.

## Putting it together

The top-level shape of the definition:

```json
{
    "format_version": 1,
    "process": "contract_review",
    "description": "Reviews a contract document: extracts key terms, flags risky clauses, routes to human legal review or auto-approves, then stores the final output.",
    "initial": "extract_terms",
    "context": { /* schema + initial from step 1 */ },
    "nodes": {
        "extract_terms": { /* step 2 */ },
        "flag_clauses":  { /* step 3 */ },
        "risk_route":    { /* step 4 */ },
        "legal_review":  { /* step 5 */ },
        "auto_approve":  { /* step 6 */ },
        "store_output":  { /* step 7 */ },
        "rejected":      { /* step 8 */ },
        "done":          { /* step 8 */ }
    }
}
```

## Running it

In Vertesia Studio:

1. **Store** → **Processes** → **New Process** (or ask the Studio Assistant to create it from this spec).
2. Save as `status: "draft"`, then open it and click **Start Run**.
3. The Start Run modal renders `context.schema` as a form, with the document picker for `contract_doc_id`. Pick a contract and choose `programmatic` unless you want a supervisor conversation involved.
4. Watch the run from **Store** → **Process Runs**: the state chart highlights the active node, the Node Inspector shows each `context_diff`, and the Conversation tab streams the two agent conversations live.
5. When `legal_review` fires, the task lands in your inbox (filtered to you and `group:legal`). Submit a decision. The process resumes.
6. `store_output` creates the summary document. `done` terminates the run.

## What to customize

- **Swap the extraction/flagging logic** by editing the agent prompts. The result schema is always derived from `writes`, so tightening or loosening the schema is as simple as editing the writes list.
- **Change the routing threshold** by editing the `when` in `risk_route`. JSON Logic supports arithmetic, comparisons, array operations.
- **Add clause-by-clause analysis** by wrapping a child agent node in a `foreach` step over `flagged_clauses`.
- **Add more human gates** — e.g. a finance-approval step for very high-value contracts — as additional `human_task` nodes with `group:finance`.

## See also

- [Node types](/processes/node-types)
- [Agent nodes](/processes/agent-nodes)
- [Observability](/processes/observability)
- [Task Inbox](/processes/task-inbox)

---

## Getting Started

Source: https://docs.vertesiahq.com/quickstart
Markdown: https://docs.vertesiahq.com/llms/quickstart.md

This guide will get you set up and ready to use the Vertesia CLI and SDK.

## Vertesia Studio

In order to create and manage your interaction you need to login to Vertesia Studio (https://cloud.vertesia.io). This is a web application in which you can create and manage your interactions.

Before using the SDK or the CLI to make requests to the platform API, you will need to
create a Project and generate an API Key from your Settings (https://cloud.vertesia.io/settings#keys).

In order to **access and use your interactions** from outside the Studio application, you can use the
Vertesia CLI (https://www.npmjs.com/package/@vertesia/cli). If you want to **integrate your interactions in your own application** you can use the
Vertesia SDK (https://www.npmjs.com/package/@vertesia/client).

Currently, the SDK is only available for JavaScript / TypeScript and code generation is only available for TypeScript. To use Vertesia in other languages you need to directly access the REST API. Please refer to the [API Reference](/api/introduction) guide for information about using the REST API.

There is a second and more efficient way to integrate the interactions in your own application by using *code generation*. **Code generation** will generate high level classes along with TypeScript interfaces to easily access your interactions. We will talk about code generation at the end of this guide.

We will cover the basics of the CLI and of the SDK in the following sections. For full documentation see the projects themselves. Let's start with a quick look at the installation and basic usage of these tools.

## Vertesia CLI

This is a command line application that can be used to access your Vertesia projects.
It was designed to fulfill the following main use cases:

* List and switch between your Vertesia projects
* List the existing interactions and execution environments
* Run interactions once or multiple times over a set of different data inputs
* Generate data inputs to run the interactions against
* Search through the history of runs to inspect detailed results

### Requirements

A TTY terminal and, as for the SDK, Node version 18 or higher is required.

### Installation

```bash
npm -g install @vertesia/cli
```

### Basic Usage

First, a profile must be created. A profile correspond to one organization/project on a vertesia environment (preview or prod). Run the following command:

```bash
vertesia profiles create
```

and follow the interactive prompts.

Once you have entered the profile information, you will be redirected to the authentication page.

Upon successful authentication, the following command:

```bash
vertesia interactions
```

will now list the interactions using the current profile.

Let's run an interaction. We will use the `run` command.

```bash
vertesia run {INTERACTION_ID}
```

This command has plenty of options. It is not the scope of this guide to explain them all.

To summarize we can run an interaction once or multiple times on a set of data inputs (specified from a file using `--input` or inline using `--data`) we can run a interaction by giving some.

When running a single interaction the response will be, by default, streamed on the console.

You can also tag runs to be able to easily search for them later using `vertesia runs`.

**Example:**

```bash
vertesia run --tags testing {INTERACTION_1_ID}
vertesia run --tags testing {INTERACTION_2_ID}
# then, later retrieve the run results having the testing_session tag
vertesia runs --tags testing
```

For more information about the commands use the `help` command or the `-h` flag on a command or check the [CLI documentation](/cli).

## Vertesia SDK

This is a JavaScript SDK that can be used in both Node.js and in the browser.

### Requirements

Node version 18 or higher is required (the fetch API is required).
It will also work with node version 17.5 by using the --experimental-fetch flag

### Installation

```bash
npm install @vertesia/client
```

### Basic Usage

Listing the projects in an organization:

```js
import {VertesiaClient} from "@vertesia/client"

const client = new VertesiaClient({
  site: 'api.vertesia.io',
  apikey: '<YOUR_API_KEY>',
})

const projects = await client.projects.list();

for (const project of projects) {
  console.log(project.name+': '+project.id);
}
```

You can see in the previous example how we initialize the `VertesiaClient` with the API key. The API key will authenticate us on the server and will select the organization to which the key belongs.

Let's list now the interactions in a project

```js
import { VertesiaClient } from "@vertesia/client"

const client = new VertesiaClient({
  site: 'api.vertesia.io',
  apikey: '<YOUR_API_KEY>',
})

const interactions = await client.interactions.list();

for (const interaction of interactions) {
    console.log(interaction.name + ': ' + interaction.id);
}
```

In the example above, you see we added a `projectId` option when instantiating the client. This is the ID of the project we want to access.

Both the API key and the project ID are set when initializing the client and will be used for all the requests made with that client.

You can change the project ID at any time by setting it directly on the client:

```js
client.project = "New Project ID"
```

Both API key and project ID parameters are required when accessing objects in a project. These parameters are sent, as custom headers, along each request to the server and are used to authenticate the user and to select the project.

Let's suppose we created an interaction named "Which Color" which is sending to the LLM an object name to get in response one of its possible colors.

Suppose `interactionId` is the ID of the interaction. We can run it as follows:

```js
import { VertesiaClient } from "@vertesia/client"

const client = new VertesiaClient({
  site: 'api.vertesia.io',
  apikey: '<YOUR_API_KEY>',
})

// Note that the interactionId argument must be a valid interaction ID which belongs to the project you are connected to.
const run = await client.interactions.execute(interactionId, {
    data: { object: "sky" }
});

console.log(run.result);
```

The response of the LLM will be:

```json
{
    "color": "blue"
}
```

## What's next?

Great, you're now set up with an API client and have made your first request to the API. Here are a few links that might be handy as you venture further:

* [Go deeper into each endpoint of the API](/api/introduction)
* [Go deeper into using the CLI](/cli)

---

## Configuration

Source: https://docs.vertesiahq.com/semantic/configuration
Markdown: https://docs.vertesiahq.com/llms/semantic/configuration.md

## Standard Document Intake Workflow

Vertesia Semantic DocPrep can be set as the default text rendition of PDF documents. When this feature is active, PDF documents are first transformed to structured markdown (or XML if specified), then the document type and its properties will be determined.

Go to Workflow > Rules and open the Standard Document Intake rule

Next, open the configuration tab and add the following:

```json
{
  "useSemanticLayer": true,
  "output_format": "markdown"
}
```

![Document Intake Rule](/semantic-layer/rule-document-intake.png)

## Configuration Options

| Option | Type | Description |
|--------|------|-------------|
| `useSemanticLayer` | boolean | Enables or disables Semantic DocPrep processing (defaults to `false`) |
| `output_format` | string | Output format: `"markdown"`, `"xml"` (defaults to `"markdown"`) |

---

## Getting started

Source: https://docs.vertesiahq.com/semantic/getting-started
Markdown: https://docs.vertesiahq.com/llms/semantic/getting-started.md

## Free Tier

Vertesia Semantic DocPrep is a pay as go service, priced per processed document pages, which comes with a one time 1000 pages free credit.

Once the free tier credit is exhausted, billing must be activated in your vertesia project in order to continue to use the service.

To activate billing, open Vertesia Studio and go to `Setting > Billings`

![Billing](/semantic-layer/billing.png)

## Vertesia Studio

Vertesia Studio makes it very simple to quickly try Semantic DocPrep with your own content.

First, go to `Objects` and upload a PDF file.

![Upload](/semantic-layer/upload-object.png)

Next, open the object by clicking on the name and go to the tab `Analyze PDF`

![Start Processing](/semantic-layer/start-processing.png)

Click on `Start Processing`

![Processing](/semantic-layer/processing.png)

When processing is completed, click on `Browse Processing Results` to see a visual presentation of the result

![Browse Result](/semantic-layer/browse-result.png)

![Annotated PDF with MD Viewer](/semantic-layer/annotated-md-view.png)

Finally, The markdown conversion is now the default text representation of the document and seamlessly replace plain text in prompt templates

![Object MD Text](/semantic-layer/object-md-text.png)

## Vertesia SDK

Vertesia Semantic DocPrep is fully supported by the Vertesia SDK and no more than a few lines of code are required to integrate the service with your own applications.

Look at [the quick start documentation](/quickstart#vertesia-sdk) to learn how to set up the client.

The first step is to upload our PDF file to Vertesia

```js
import { StreamSource, VertesiaClient } from "@vertesia/client";
import { createReadableStreamFromReadable } from "node-web-stream-adapters";

// Initialize client
const client = new VertesiaClient({
  apikey,
});

const stream = createReadStream(<FILE_PATH>);
const content = new StreamSource(
  createReadableStreamFromReadable(stream),
  path.basename(<FILE_PATH>),
  "application/pdf",
);
const object = await client.objects.create({
  content: content,
});
```

Next, the file analysis can be triggered using the `start` function.

```js
// Run analysis
const analysisRun = await client.objects.analyze(object.id).start({
  features: [],
});
console.log("Analysis Started", analysisRun);
```

Processing is asynchronous and the status can be fetched using the `getStatus` function.

```js
let analysisStatus = await client.objects.analyze(object.id).getStatus();
console.log(analysisStatus)
```

Once processing is done, the complete result can be fetched with the `getResult` function.

```js
// Get Results
const results = await client.objects.analyze(object.id).getResults();
console.log(results.document);
```

---

## Overview

Source: https://docs.vertesiahq.com/semantic/overview
Markdown: https://docs.vertesiahq.com/llms/semantic/overview.md

Vertesia Semantic DocPrep is a generative AI powered service that transforms documents into structured markdown or XML files in order to dramatically improve processing and understanding of the document's content by LLM models.

![Annotated PDF with MD Viewer](/semantic-layer/annotated-md-view.png)

## What problem does it solve?

When Large Language Models (LLMs) are provided with a simple plain text conversion of a PDF or PowerPoint file, critical information conveyed by the document's layout and formatting is lost. For example, the way a heading signals the start of a new topic, or how data is organized in rows and columns within a table – this structure provides essential context for understanding. Without this information, LLMs can struggle to grasp the true meaning and relationships within complex documents, leading to less accurate processing and difficulties in extracting specific details. For instance, an LLM might not correctly associate a paragraph with its corresponding heading or accurately interpret the data presented in a multi-layered table.

Vertesia Semantic DocPrep intelligently transforms your documents, starting with PDFs, into structured markdown or XML files, creating a semantically aware representation of your documents. Rather than relying on plain-text or OCR extraction alone, DocPrep reads each page with a vision model, so headings, columns, tables, and visual elements are interpreted as they actually appear on the page. OCR is applied where a page needs it, but it is one input to the reading, not the whole of it. By explicitly encoding the document's layout and formatting within the structured representation, Vertesia preserves the crucial contextual cues that are otherwise lost in plain text. This enhanced structure acts as a clear roadmap for LLMs, dramatically improving their ability to understand the document's content, especially when dealing with intricate layouts and large tables.

Vertesia directly addresses the limitations of processing plain text, paving the way for more accurate, reliable, and insightful interaction and automation with your documents:

* Accurate Information Extraction: Knowing the structure helps LLMs extract specific pieces of information more reliably, especially from tables or complex structures.

* Deep Linking and Referencing: Precise layout information (like bounding boxes) enables the creation of deep links to specific locations within a document.

* Reduced Hallucinations: By grounding the LLM in a structured representation of the document, you can minimize the chances of it generating inaccurate information or "hallucinating" content that isn't actually present or is misinterpreted due to a lack of structural understanding.

## Main features

Vertesia Semantic DocPrep includes the following features:

* Read each page with a vision model that interprets layout, tables, headings, and visual elements directly – going beyond plain OCR text extraction, with OCR applied where a page needs it
* Transform PDF files into structured markdown (default) or XML files which can then easily be used in all your interactions and workflows
* Generate annotated renditions of pages
* Extract tables contained in documents using your own specific target schemas

## Supported Format

As of today, the service only supports the `PDF` format.

---

## Studio Assistant

Source: https://docs.vertesiahq.com/studio/assistant
Markdown: https://docs.vertesiahq.com/llms/studio/assistant.md

The **Studio Assistant** is an AI helper available from any page in Vertesia Studio. Think of it as a collaborator who knows the product — it can design interactions, draft prompts, build content types, create agents, author processes, and set up databases for you.

It lives behind the small bot icon in the top bar. Click it, a side panel opens, and you're in a chat. The assistant carries context about the page you're on, so asking "help me structure this prompt" on a prompt-editor page does the right thing without you having to explain which prompt.

## Where it lives

- **Top-bar icon** — present on every Studio route. Toggles the panel open/closed.
- **Side panel** — mounted inside the Studio module. Shows the conversation, a small config drawer (environment + model), and the streaming stop/new-chat controls.
- **Per user + project persistence** — the selected environment, model, and active `agent_run_id` are saved in `localStorage` keyed by `studio.assistant..`. Resuming Studio later resumes the same conversation.

## How it's built

Under the hood, the assistant is a standard Vertesia agent run targeting the `StudioAssistant` system interaction. Three things make it feel different from a generic agent:

1. **Skill-driven tools.** The assistant ships with only `learn_*` skills enabled by default (studio_overview, interaction_design, prompt_engineering, content_modeling, agent_creation, …). Calling a skill reads its guidelines and unlocks the actual domain tools. The assistant can't, for example, create a content type before calling `learn_content_modeling` — the skill's `related_tools` is what opens that capability.
2. **Route context on every turn.** The panel derives a coarse `{ route_path, resource_type, resource_id }` from the current URL and injects it into the conversation. The assistant knows if you're on `/studio/interactions/abc123` it's talking about an interaction, and it can fetch that interaction without you pasting the id.
3. **Configurable environment + model.** A small config strip lets you pick which execution environment and model drive the assistant. Defaults to the project's default environment. This is the same config surface as `agent_runner`.

## What it's good at

- **Authoring.** "Design an interaction that extracts line items from invoices", "draft a prompt for classifying support tickets", "create a content type for legal contracts". The assistant follows the skill for each task type — e.g. `learn_interaction_design` before proposing a schema.
- **Explaining.** "What does this prompt do", "why is this agent failing to call the tool I declared", "what are my options for routing in a process". Each domain skill carries the current best-practice guidance.
- **Orchestrating authoring.** "Build me a contract-review process" loads `learn_process_design` once the assistant has enough context, then drafts, validates, and saves the definition — composing with the other system interactions (`sys:ExplainProcess`, `sys:ProcessAgentNode`) rather than competing with them.

## Process authoring

For process work, the assistant should load `learn_process_design` before drafting or changing anything. From there it can:

- draft a full process definition from your description;
- validate a draft with `validate_process`;
- create or update a draft process definition;
- publish only after explicit user confirmation;
- explain a selected process, node, or guard through `sys:ExplainProcess`;
- improve process-board layout through `LayoutProcessDefinition`.

When improving layout from a screenshot, the image is only visual evidence. The assistant still needs the actual process definition and should only touch visual authoring fields: node titles/descriptions, `human_description`, `metadata.phase`, `metadata.lane`, `metadata.order`, optional visual position metadata, and transition/branch labels. It should not change runtime behavior.

When designing node behavior, the assistant should use real project capabilities. For general open-ended agent work, use `sys:GeneralAgent` or a custom agent interaction. For normal process worker-agent nodes, let the engine use `sys:ProcessAgentNode` unless there is a concrete custom interaction contract.

## What it's not

- **Not a replacement for the Agent Runner.** For running a production agent against a known interaction, use the [Agent Runner](/agent-runner/overview) — you get the full conversation page, artifacts, observability, and restart/fork controls.
- **Not for long-running batch work.** The side panel is designed for interactive assistance. For a batch job or a scheduled process, author the process and run it.
- **Not silent.** It always uses whichever model you configured; no background usage. If you close the panel mid-turn, the active run continues (Temporal is durable) but the UI disconnects.

## Invoking it

- Click the bot icon in the top-bar banner.
- Keyboard: any Studio page — open the side panel, type. The assistant sees the page context automatically.
- From code in the UI: `emitStudioAssistant('open' | 'close' | 'toggle')` dispatches a `studio-assistant:toggle` window event. The panel listens. This lets other UI surfaces (e.g. a "Ask the assistant" button on an error banner) open the helper without prop-drilling.

## Starting a new chat

The panel has a "New chat" control that creates a fresh agent run and updates the stored `agent_run_id`. Old runs are durable — their history is preserved in the backend — but won't be revisited through the assistant UI once you start a new one. For anything important, save the output or the agent-run id before starting over.

## Steering it

A few patterns make the assistant noticeably more useful:

- **Tell it what skill to use.** "Use `learn_interaction_design` for this" front-loads the right guidance.
- **Give it enough context upfront.** If you want an interaction designed for a specific use case, state the inputs and expected outputs in the first message.
- **Ask for tool calls explicitly.** "Create the interaction" is clearer than "could you make the interaction". The assistant is happy to narrate; if you want action, ask for action.
- **Correct it out loud.** When it drifts, say so. Skills and tool declarations constrain it, but prompt-level nudges are the cheapest way to steer.

## See also

- [Agent Runner](/agent-runner/overview) — the full-page agent runner for production agents.
- [Authoring Processes](/processes/authoring) — process authoring with the Studio Assistant and the `learn_process_design` skill.
- [GenAI Tasks — Interactions](/studio/interactions) — what the assistant helps you author.

---

## Interactions

Source: https://docs.vertesiahq.com/studio/interactions
Markdown: https://docs.vertesiahq.com/llms/studio/interactions.md

//import YoutubeVideo from '../../../components/YoutubeVideo';

# Interactions

The interactions are a core concept of the Vertesia Platform.
Roughly speaking, an interaction is a composition of parametrized prompts which define the task a target LLM is requested to perform.
In addition, an interaction can define a schema to structure the response of the LLM.

To understand interactions it is important to understand first the following concepts:

1. **Parametrized Prompt**.

A parametrized prompt is a message template and a schema which defines which kind of parameters are required to render the template as a plain text message.
Templates can use **Handlebars** syntax (`{{variable}}`, recommended) or **JS Templates** (`${variable}`, for advanced composition).
The most simple prompt is a static prompt which does not require any parameter.
Each parametrized prompt defines a schema which describes the parameters required to render the prompt.

Prompts can be reused between interactions.

2. **Prompt Roles**

Each prompt has a role. The following roles are defined:

* **safety** - The safety prompt have the highest priority and are always rendered first. They are used to prevent the LLM to generate unsafe content.
* **system** - The system prompt is used to define the context of the interaction. For example, the system prompt can be used to define the persona of the LLM.
* **user** - The user prompt is used to define the user input.
* **assistant** - The assistant prompt is used to define the assistant (i.e. LLM) input.

3. **Prompt Formatters**

Parametrized prompts are rendered and assembled together to form the intermediate prompt which is describing the task the LLM should perform.
This intermediate prompt is an array of **Prompt Segments**, similar to OpenAI's prompt format: plain text messages with a role attribute.

As each LLM expect the prompt to be formatted in a certain way to understand the task, the prompt segments must be rendered into a final prompt
to fit with the LLM's prompt format (properly tag the system prompt, the user messages, the application messages, etc.).

Vertesia natively offers the following prompt format:

* **openai**: array of plain text messages with a role attribute.
* **llama2**: string with a special syntax to tag the prompt segments.
* **claude**: string with "Human:"" and "Assistant:" to tag the prompt segments.
* **GenericLLM** string with User and System tags, well suited for most other LLMs (Mistral, AI21, Cohere, etc.)

If you need a specific format, please reach out to us to review how to do it.

4. **Input Schema**

The input schema defines the structure of the data input which is required to render the prompt segments. \
This schema is implicitly defined by the concatenation of the parametrized prompt schemas.

5. **Output Schema**

An output schema can be defined if the LLM should respond using a JSON structured response. \
Both input and response schemas are defined as JSON schemas (https://json-schema.org/).
The executor will validate the output data using the defined schemas.

6. **Environment**

An **execution environment** is a configuration used to describe a target LLM. \
Interactions may specify a default environment which will be used to execute the interaction.

Apart the default environment, interactions may define default configuration properties like the model or the temperature to use.


## Creating an Interaction

### Defining an interaction's expected result

This is about defining what you expect from a functional perspective, in terms of result.


### Creating an interaction to summarize an input text

Let's see how to create a simple interaction that summarizes any input text.


### Creating an interaction to summarize an input document

As an alternative to the previous interaction, you may want to summarize a document previously stored in Vertesia's Content Store, which is by the way a very convenient way to implement RAG (Retrieval Augmented Generation).

Imagine you’re drafting a contract, preparing for litigation, or simply searching through thousands of legal documents to find the most relevant clauses and precedents. Retrieval Augmented Generation (RAG) is an advanced AI approach that can greatly streamline this process by combining two key abilities:

1. **Retrieval of Relevant Information**:
The AI system searches through vast enterprise databases, internal records, case files, and legal texts to pull out the most relevant pieces of information needed for your task. Think of it like having a super-efficient research assistant who quickly digs through your firm’s archives to find the exact document or detail you need.

2. **Generation of Tailored Content**:
Once the key pieces of information are retrieved, the AI system uses its language generation capabilities to create a draft or summary that combines this data seamlessly with its own pre-existing knowledge. This means you get a well-organized, accurately referenced piece of text that can serve as a foundation for your legal work.


### Generating an interaction from a sentence

Instead of configuring an interaction from scratch, you may ask Vertesia to generate one from a simple sentence. And then fine tune it.


### Adding comments to an interaction's result schema

An `Interaction`'s result schema is made of output parameters corresponding to what you expect to get from the interaction. It possible to comment those output parameters:
- either for documenting what they do
- or for helping the underlying LLM understanding what you expect, precisely


## Executing Interactions

Once you created an interaction you can test your interaction from the `Playground` tab of the `Interaction` page.
The reason of using Vertesia is to use the interaction you created in a real application you own.

You can do this by invoking the **REST API** to execute the interaction given the interaction ID, a data input object and optionally some specific execution environment configuration.
If your application is a JavaScript application you can use the **Vertesia SDK**.

## Execution Runs

The interaction execution will create an execution `Run` object, which is used to track the execution progress and, when completed, to store the results if successful or the error if it failed. \
Thus, you can find later an execution `Run` to analyse or even to reuse the results.

In order to easily find execution `Run` objects you can tag an `Interaction` execution with one or more tags.

## Interaction Versions

When you create an interaction it will have a `draft` status and will be at `version 1`. \
You continue working on it, testing the results with different environments and data inputs and finally you think the interaction is ready to be used in production. \
You may want then to save this version of the interaction as a read only copy of the draft interaction. This may be achieved by **publishing** the interaction.

Publishing an interaction will create a read only copy at the same version with one of the draft version (in our case at `version 1`), then it will increment the draft version at `version 2`. \
So, the draft version is always the version you are working on and the published versions are copies frozen in time of your intermediate versions of the interaction.

When publishing an interaction, all prompts referenced by the interaction are published too, by creating a read only copy of the current state of the prompts.

Also, when publishing an interaction you can choose to make the published interaction **public**. By doing so, you enable other organizations to **fork** your interaction and create their own version of it.

---

## Prompts

Source: https://docs.vertesiahq.com/studio/prompts
Markdown: https://docs.vertesiahq.com/llms/studio/prompts.md

LLMs rely on a prompt they receive, and return a reply accordingly. Let's go deeper into how to configure a prompt and what can be achieved with it.

We have seen in the **Quickstart/Concepts** section that an `Interaction`'s prompt is made of one or many parametrized `Prompt Segments`.

A `Prompt Segment` has a role, such as `System` or `User`. \
System - This role is used to define the context of the interaction. For example, the system prompt can be used to define the persona of the LLM (*"You are a seasoned expert in legal affairs"*). \
User - This role is used to define the user input (*"Does this contract comply with corporate rules?"*).

Several `Prompt Segments` can thus be combined to make a consistent global prompt.


Each `Prompt Segment` can define a `Prompt Schema` made of zero or many `Input Parameters`.

An `Input Parameter` can have one of the following types:
- `string`
- `number`
- `integer`
- `boolean`
- `object`
- `any`
- `text`
- `media`
- `document`
- and the equivalent arrays, such as `string[]`

**Example : definition of a structured table of movies**

```bash
movie_input_table : object[]
  ├─ movie_title    : string
  ├─ movie_director : string
  └─ movie_synopsis : string
````

A `Prompt Segment` may refer to `Input Parameters` by injecting their value into the prompt's message.

## Defining and using simple prompt segment input parameters

In the following example we add two `Input Parameters` to a `Prompt Segment` intended to request the summary of a document text into a target language.


## Reusing prompt segments

Vertesia's `Prompt Segment Library` allows easily reusing them to serve new needs and use cases. In the following example, we fork an existing `Prompt` related to generic contracts in order to slightly adapt it to supplier contracts, and then add it to a relevant `Interaction`.


## Adding dynamicity and conditionality to a prompt

Vertesia supports two template engines for dynamic prompts: **Handlebars** (recommended) and **JS Templates**. Both allow you to inject input parameters, add conditionals, and iterate over data.

### Handlebars Templates (recommended)

Set the prompt segment's `content_type` to `"handlebars"`. Handlebars uses double curly braces for variable substitution with a clean, readable syntax.

**Variable substitution:**

```handlebars
Summarize the following {{document_type}} in {{target_language}}:

{{document_text}}
```

**Conditionals:**

```handlebars
Analyze the following contract.
{{#if focus_area}}
Focus specifically on: {{focus_area}}
{{/if}}
{{#if strict_mode}}
Flag any non-compliant clauses.
{{else}}
Provide a general overview.
{{/if}}
```

**Iterating over arrays:**

```handlebars
Review the following items:
{{#each items}}
- {{this.title}}: {{this.description}}
{{/each}}
```

**Helpers and System Variables:**

| Name | Description | Example |
|---|---|---|
| `_now` | Returns the current ISO timestamp | `Report generated at {{_now}}` |
| `_model` | The model ID used for the current execution | `Model: {{_model}}` |
| `stringify` | Converts a value to its JSON representation | `Input data: {{stringify data}}` |
| `if` / `unless` | Conditional rendering | `{{#if flag}}...{{/if}}` |
| `each` | Iterate over arrays or objects | `{{#each list}}...{{/each}}` |
| `with` | Change the evaluation context | `{{#with user}}{{name}}{{/with}}` |

### JS Templates (advanced)

For advanced composition and templating needs, JS Templates provide the full power of JavaScript. Set the prompt segment's `content_type` to `"jst"`. JS Templates use JavaScript string interpolation and run in a sandboxed environment. Use JST when you need complex data transformations, CSV processing, date manipulation, or programmatic prompt construction that goes beyond what Handlebars conditionals and loops can express.

```javascript
`Summarize the following text in ${target_language}:

${document_text}`
```

JS Templates support full JavaScript control flow and array operations:

```javascript
`Review the following ${items.length} items:
${items.map((item, i) => `${i + 1}. ${item.title}: ${item.description}`).join('\n')}

${strict_mode ? 'Flag any issues found.' : 'Provide a general overview.'}`
```

JS Templates provide a utility object `_` with the following helpers and system variables.

**Helpers and System Variables:**

| Name | Description |
|---|---|
| `_.stringify(value)` | JSON serialization |
| `_.loadCsv(text)` | Parse CSV text into an array of objects |
| `_.jsonToCsv(data)` | Convert an array of objects to CSV format |
| `_.addLineNumbers(text)` | Prefix each line with its line number |
| `_.dayjs(date?)` | Date manipulation via [Day.js](https://day.js.org/) |
| `_model` | The model ID used for the current execution |

The template must return a string as its final expression.


## Accessing properties of a stored document from a prompt

**Vertesia Content Store** is a convenient way to store contents within the platform - typically you knowledge bases, such as corporate policies, operational procedures, standards, or suppliers contracts.

It makes RAG (Retrieval Augmented Generation) much easier and also allows content metadata to be generated and stored in the platform. You may think about a first interaction extracting metadata from raw contracts, and a second performing specific analysis only on a subset of relevant contracts.

Stored `Content Objects` may thus not only contain text, but also metadata (properties). Should you need to access such metadata from a prompt, here is the way to achieve it.

The following example illustrates how to retrieve the `effective_date` of a stored contract from a `Prompt Segment`.


## Getting a suggestion of improvement for a prompt

What if you could get in a snap suggestions of improvement for your prompts? Here we go.

---

## Release Notes Generation

Source: https://docs.vertesiahq.com/use-cases/release-notes-generation
Markdown: https://docs.vertesiahq.com/llms/use-cases/release-notes-generation.md

This page explains how to generate release notes using LLM and Vertesia. It is split into three sections:

1. **Data Collection:** how to collect information using the Recipe, Memory Pack, and the `vertesia` CLI.
2. **Generation:** how to generate release notes using Interaction and a Memory Pack in Vertesia
3. **Integration:** how to integrate into GitHub Actions to further automate the generation


After reading this page, you should be able to:

1. Write a Recipe to interact with other CLIs, such as `git` and `gh` (GitHub)
2. Build a Memory Pack for the generation of release notes
3. Write an Interaction to generate release notes with the use of a Memory Pack
4. Use the `vertesia` command line to run the actual execution
5. Automate the generation of release notes in GitHub Actions

## Prerequisites

You need to install the latest version of vertesia CLI to execute the Recipe and build the Memory Pack. Also, it requires using a TTY terminal, and Node.js version 18 or higher.

```sh
npm -g install @vertesia/cli
```

## Step 1: Data Collection

Before interacting with an LLM for the generation, you need to collect data. This section explains how to collect information from GitHub Issues and GitHub pull requests using a script, and how to store the collected results as a data snapshot. It explains different instructions related to the data collection and the internal structure of the snapshot. It also mentions how to implement similar solutions for other project management tools.

First of all, you need to understand two terminologies related to this step: Recipe and Memory Pack.

* A Recipe is a text document written in TypeScript that contains all the instructions for building the data snapshot. A Recipe can be executed by the command line interface `vertesia`. This is similar to Dockerfile for those who are familiar with Docker.
* A Memory Pack is a data snapshot that presents an immutable context for Large Language Models. A Memory Pack is portable: it can be used for multiple interactions with LLMs.

### 1.1 Writing the Recipe

A Recipe is a TypeScript file used to build a Memory Pack. It can contain any JavaScript code, but it must use the built-in Memory Commands to create the Memory Pack, a TAR file. You will see some of the instructions in the sections below. For the complete reference, please read the README of the Git repository [vertesia/memory](https://github.com/vertesia/memory/).

### 1.1.1 Importing Commands

Before running any commands in the Recipe, you can import those commands from the JavaScript library `@vertesia/memory-commands`. In this tutorial, we are going to use the following commands:

* `copy` — copy a file to the Memory Pack.
* `copyText` — copy an inline text into the Memory Pack a file
* `exec` — execute a shell command
* `tmpdir` — create a temporary directory
* `vars`  — retrieve the variables specified by the user

You can import them as follows:

```ts
import {
    copy,
    copyText,
    exec,
    tmpdir,
    vars,
} from "@vertesia/memory-commands";
```

### 1.1.2 Extracting Input Parameters

Then, you need to extract information from the variables. Here the variables "start" and "end" represent the range of the release notes, where "start" stands for the earliest Git reference or commit SHA for this release, and "end" is the newest Git reference or commit SHA for this release:

```ts
const { start, end } = vars();
```

Then we create a temporary directory as the current working directory for the data collection process. We can add temporary files into this directory and package them as a Memory Pack.

```ts
const cwd = tmpdir();
```

### 1.1.3 Collecting Information From Git

We use the Memory Command exec to execute a bash command to extract information from git. It retrieves all the commits between the “start” and the “end” of the release and shows the title of each commit in a one-line format. Then, we extract the issues and pull requests from the commit message. If a message contains a word starting with a hashtag followed by a sequence of numbers, it will be found by our command. For example, we can find “123” from the message “Fix #123”. You can imagine other implementations, but what matters is the possibility of invoking any arbitrary shell command using the exec instruction. This can be very useful for building a data pipeline.

```ts
const hashtagIds = await exec(`git log ${start}..${end} --oneline | grep -o -E '#[0-9]+' | sed 's/#//'`) as string;
```

Then, we can use built-in JavaScript syntax to perform string manipulation. Here, we split the text by the newlines (`\n`), convert them into numbers, and finally, sort the numbers in ascending order.

```ts
const unsortedReferenceIds = new Set(`${hashtagIds}`.trim().split("\n").map(v => v.trim()).map(Number));
const referenceIds = Array.from(unsortedReferenceIds).sort((a, b) => a - b);
```

You can also extract the code difference between the beginning and end of the release to see what has been changed. This can be useful for the LLM to improve the description of the items in the release notes based on the actual code changes. It can also improve the categorization of the changes because you can see the file paths and the function signatures there.

```ts
await exec(`git diff --submodule=diff ${start}...${end} -- ':!docs/changelog/*' > ${cwd}/range_diff.txt`)
copy(`${cwd}/range_diff.txt`, "range_diff.txt");
```

### 1.1.4 Collecting Information From GitHub

Here, we use the GitHub CLI `gh` to retrieve the content of each reference. A reference can be either a pull request or an issue. Since we don’t know the type, we try one type and fall back to the other if needed. More precisely, we use the `gh pr view` command to retrieve the content. If it is empty, the reference is an issue rather than a pull request. So we would try again using the `gh issue view` command. Then, we use the Memory Command `copyText` to copy the content into a file in the Memory Pack. The reference is injected into a string using template laterals.

```ts
for (const reference of referenceIds) {
    let content = await exec(`gh pr view ${reference}`) as string;
    if (content) {
        copyText(content, `pull_requests/${reference}.txt`);
    } else {
        content = await exec(`gh issue view ${reference}`) as string;
        copyText(content, `issues/${reference}.txt`);
        issueIds.delete(`${reference}`);
    }
}
```

### 1.1.5 Collecting Information From Other Sources

Since we can run arbitrary commands using the Memory Command exec, you can interact with other sources easily. For example, you can fetch information from JIRA using the [JIRA CLI](https://github.com/ankitpokhrel/jira-cli); you can fetch information from GitLab using [GitLab CLI](https://gitlab.com/gitlab-org/cli); and many more. This approach works well when you don’t need to manipulate the response payload returned by the command line. For example, you don’t need to extract information from JSON or YAML.

### 1.1.6 Exporting Metadata

At the end of the file, you must export the Memory Pack Metadata. This is a JSON object which holds the properties to be used when interacting with LLMs. In our case, we are exporting two properties: the `from_version` and the `release_version` which maps to the variables `start` and `end` of the Memory Pack.

### 1.2 Building the Memory Pack

Now, you can use the `vertesia memo build` command to build your memory pack. You can specify the location of your memory pack, which can be local or remote. If you use the `memory:` prefix, it will be stored in Google Cloud under the bucket of your project. Otherwise, it will be stored on your local host.

```sh
vertesia memo build \
    --out "memory:release_notes/my_package-1_2_0" \
    --var-start "my_package/v1.1.0" \
    --var-end "my_package/v1.2.0" \
    recipes/release-notes.ts
```

Note that you need to use prefix `memory:` for the production use-cases. Otherwise, Vertesia Cloud cannot read the data and inject them into the prompts when interacting with LLMs.

When you run the command, you should see logs like these in your console:

> ```
> Retrieving issues between my_package/v1.1.0 and my_package/v1.2.0...
> Running: git log my_package/v1.1.0..my_package/v1.2.0 --oneline
> Running: grep -o -E #[0-9]+
> Running: sed s/#//
> Command: git log my_package/v1.1.0..my_package/v1.2.0 --oneline | grep -o -E '#[0-9]+' | sed 's/#//' exited with status 0
> Running: git log my_package/v1.1.0..my_package/v1.2.0 --oneline
> Running: grep -o -E \([0-9]+\)
> Running: sed s/[()]//g
> Command: git log my_package/v1.1.0..my_package/v1.2.0 --oneline | grep -o -E '\([0-9]+\)' | sed 's/[()]//g' exited with status 0
> Found 182 references
> Processing reference #359
> Running: gh pr view 359
> Command: gh pr view 359 exited with status 0
> Processing reference #474
> Running: gh pr view 474
> ...
> ```

Once created, you can inspect the Memory Pack using tools that can manipulate a TAR file, such as the tar command or any GUI tools.

### 1.3 Recap

In "Step 1: Data Collection", we saw the first part of the generation of release notes. We saw how to create a small data pipeline to collect information using Recipe, a standalone TypeScript file with Memory Commands. Then, we saw how to build the Memory Pack using the `vertesia` command. In the next step, you will learn how to implement the interaction with LLM.

## Step 2: Generation

This section explains how to generate release notes with Vertesia Studio and Memory Packs. It will explain the features of Interaction and Prompts in Studio; it will explain how to use a Memory Pack inside an Interaction; and finally, how to run the Interaction from the command line.


After reading this page, you should be able to:

* Write an Interaction to generate release notes with the use of a Memory Pack
* Use the `vertesia` command line to run the actual execution

### 2.1 Create An Interaction

An Interaction is a reusable function that can be invoked to perform a specific task related to large language models (LLMs). It contains one or multiple prompt segments, and uses a model for the execution. The prompts are used to provide context, instructions, and information related to input and output. They can be shared between different interactions for re-usability.

In the case of the generation of release notes, we can have two prompts: one system prompt for providing the context and a user prompt for describing the instructions.

### 2.1.1 Provide Context

You can use a system prompt to create the context of the generation. You can give a persona to the prompt as the initial information. This can be relevant background or scenario, which helps the model generate responses that align with the desired context. For example, you can mention your company's name and mission; you can require the LLM to play the role of a Release Manager, specializing in Software-as-a-Service (SaaS) products; you can specify the target audience; etc.

### 2.1.2 Provide Instructions

Then, you can use a user prompt to specify the instructions, the input data, and the output format for the generation.

For the instructions, you can specify the actions and logic you want the LLM to perform. For example, you can specify the sections of the release notes, e.g. “New Features”, “Major Changes”, “Improvement and Fixes”, based on labels of your project management tools (e.g. JIRA or GitHub Issues), code path, applications, the importance of the changes, etc. You can specify the structure inside a section and how each item is computed. You can also provide specific treatment for the LLM for content generation, such as how to handle the situation when one product change is matched to multiple pull requests, when one commit has multiple authors, etc.

Note that you can use Prompt Schema to inject variables into your prompt when choosing the content type as “JS Template”. This is useful for making the instructions accurate to your current task. For example, you can inject `previous_version`, `release_version`, to specify the range of the changes.

```js
return `
## Instructions

You need to write release notes on release ${release_version}, highlighting
changes between the current version ${release_version} and the previous version
${previous_version}.

...
`
```

### 2.1.3 Provide Input Data

For the input data, you can specify all kinds of information related to the release notes, including the pull requests, the commit log, the code difference, or anything else you feel is relevant to the generation. Similar to the instructions, they can be defined in Prompt Schema and injected into a JS Template. These variables are injected during the execution as parameters. Here is what it looks like:

```js
return `...

## Input Data

Here are the input data you can take into account for the content generation,
including Git commits, code differences, GitHub pull requests, and GitHub issues.

Here are the commits between the version "${previous_version}" and the version
"${release_version}", generated by the git-log command:

<commits>
${commits}
</commits>

Here is the code difference between the version "${previous_version}" version and
the version "${release_version}", generated by the git-diff command:

<code_diff>
${code_diff}
</code_diff>

...
`
```


But now imagine that you need to provide hundreds of commits, thousands of lines of code changes, and other pieces of information before running the Interaction. It is pretty challenging. You may also need to deal with data serialization issues if you run the command in your terminal. It's painful and not part of your business logic.

With the Memory Pack, it simplifies the data injection process. When running the Interaction from the `vertesia` CLI, you can use a specific entry `@memory` to provide the path of the Memory Pack to use, relative to your project, to provide all the pieces of information collected earlier. Then, you can map those entries to the prompts. For example, mapping all the entries of the Memory Pack using the expression `"@": "@"`, or mapping the content of the commit log to the variable `commits` using the mapping `"commits": "@content:commits.txt"`. We will see more details later on this page.

### 2.1.4 Provide Output Indicator

In the prompt, you can also specify the structure of the generated content. This can be related to the length of the description of each change, the metadata to be included (application, link of the ticket, author, date, ...), the styling (bold, italic), the structure of the content, or any other instructions for the output.  Giving those details allows you to have more control over the format.

### 2.1.5 Publish Interaction

Once you are satisfied with the Interaction. You can publish it so that you can run the Interaction from the CLI.

### 2.2 Run Interaction

You can run your Interaction by full name. The full name is composed of an optional namespace, a required endpoint name, and an optional tag or version. Examples: `name`, `namespace:name`, `namespace:name@version`:

```sh
vertesia run [options] <interaction>
```

In the case of the release notes, you need to provide mappings as inline data via the option `-d`, `--data`. Here is an example:

```sh
mappings=$(cat << EOF
{
    "@memory": "release-notes/${GITHUB_RUN_ID}-${GITHUB_SHA::7}",
    "@": "@",
    "issues": "@content:issues/*",
    "pull_requests": "@content:pull_requests/*",
    "code_diff": "@content:range_diff.txt",
    "commits": "@content:commits.txt"
}
EOF
)

vertesia run GenerateReleaseNotes -d "$mappings"
```

In this example, we have:

* **`@memory`**: the path of the Memory Pack (`release-notes/${GITHUB_RUN_ID}-${GITHUB_SHA::7}`). It is the location where the Memory Pack is stored. It is relative the root of the cloud storage bucket (Google Cloud Storage, Amazon S3) specified when you built your Memory Pack during the data collection process. Here, we use the GitHub Actions Run ID combined with the GitHub commit SHA to provide additional build time metadata for the Memory Pack.
* **`@`**: the content mappings. It matches all the `export` attributes defined in the Memory Pack to the attributes with the same name, defined in the Parameters Schema of the Interaction.
* **`issues`**: a mapping for the multi-value string field "issues" (`string[]`). This mapping extracts the content of each file located under the path `issues/` of the Memory Pack, and map the content as a string for an element in the array.
* **`code_diff`**: a mapping for the simple string field "code_diff" (`string`). This mapping extracts the content of the `range_diff.txt` file and assigns it to the variable `code_diff`.
* And similar mappings for other fields

If you need to troubleshoot the execution, you can visit the "Runs" tab in Vertesia Studio. It provides detailed information about the input data, output result, the schema, the run status, etc.

### 2.3 Recap

In this section, we saw the second part of the generation of release notes: the use of LLM. We saw how to create an Interaction using Prompts with context, instructions, input data, and output indicators. We also saw how to run the Interaction using the `vertesia` command line. In particular, how to run the execution with a Memory Pack.


## Step 3: GitHub Integration

In this section, we will discuss how to integrate the generation of release notes into GitHub Actions, a popular CI solution. We will see how to set up `vertesia` CLI in GitHub Actions. Then, we will discuss how to use it to build the Memory Pack and run the Interaction to generate the content.

### 3.1 Generating API Key

You need to create an API key in your Vertesia project ([https://cloud.vertesia.io](https://cloud.vertesia.io)) before using Vertesia's service in GitHub Actions.

* Go to the page "Settings" on the left sidebar, then go to the tab "API Keys"
* Create a new API key, such as "Release Notes Generation", with the role `executor`.

More precisely, the role `executor` gives the API permission to upload Memory Packs and execute Interactions. You can also use a more permissive role, but the role must be authorized to perform these two operations. The type should be "Secret Key (sk)" and be meant to be used in a private location, not from a browser or any public location that exposes the secret.

### 3.2 Automate Generation with GitHub Actions

Once the API key is created, the next step is implementing the GitHub Actions to automate the generation. In this section, we will go through the most important actions. These code blocks are code snippets of a [GitHub Workflow file](https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions).

### 3.2.1 Prepare Input Parameters

We need two parameters for generating the release notes: the target tag and the previous tag. The target tag is the release target, a commit SHA or a Git reference (tag, branch) for which we want to write the release notes. The previous tag is the previous version that had been released, which serves as the reference for the comparison.

```yaml
on:
  workflow_dispatch:
    inputs:
      target_tag:
        description: Target tag
        required: true
        type: string
      previous_tag:
        description: Previous tag
        required: true
        type: string
```

### 3.2.2 Set up Vertesia CLI

You need to set up Node.js to your CI environment. This is a prerequisite for installing the `vertesia` CLI.

```yaml
- uses: actions/setup-node@v4
  with:
    node-version: 24
```

Once done, you can install the [`vertesia`](https://www.npmjs.com/package/@vertesia/cli) CLI globally:

```yaml
- run: npm install -g @vertesia/cli
```

Then, you need to add a profile to the `vertesia` so that you can interact with your project. As of 27 Jan 2025, we don't support initializing a new profile from a headless browser. Therefore, you have to initialize it manually by adding the following structure into the `~/.vertesia/profiles.json` file:

```sh
cat <<EOF >> ~/.vertesia/profiles.json
{
    "default": "github-actions",
    "profiles": [
        {
            "name": "github-actions",
            "config_url": "https://cloud.vertesia.io/cli",
            "account": "${VT_ACCOUNT}",
            "project": "${VT_PROJECT}",
            "studio_server_url": "https://api.vertesia.io",
            "zeno_server_url": "https://api.vertesia.io",
            "apikey": "${VT_API_KEY}"
        }
    ]
}
EOF
```

where:

* `VT_ACCOUNT` is the account ID in Vertesia
* `VT_PROJECT` is the project ID in Vertesia
* `VT_API_KEY` is the API key generated in Vertesia

You can find those pieces of information in your Vertesia project.

The API key is a sensitive information. There are several ways to inject it securely into your GitHub Actions. You can use the GitHub Secrets to store it as a repository secret or an organization secret, then you can use it as an environment variable. Or, you can rely on third party services to store the sensitive information, such as using Google Secret Manager, Amazon Secret Manager, Amazon Systems Manager Parameter Store, etc. Here is an example using secrets in GitHub:

```yaml
- name: Set up vertesia CLI
  env:
    vertesia_api_key: ${{ secrets.VT_API_KEY }}
  run: ...
```


Once done, you can use the following action to verify if the profile is correctly recognized by the CLI:

```yaml
- run: vertesia profiles
```

And you are expected to see an active profile "github-actions" being created:

```sh
github-actions ✔
```

### 3.2.3 Gather information

Now you are ready to build the Memory Pack, using the `vertesia memo build` command where:

* the Memory Pack will be uploaded to your Vertesia bucket under the path `release-notes/` followed by the GitHub Run ID and the commit SHA.
* the `start` and `end` variables define the range of the release notes

```yaml
- name: Gather information
  run: |
    vertesia memo build \
        --out "memory:release-notes/${GITHUB_RUN_ID}-${GITHUB_SHA::7}" \
        --var-start "${{ env.PREVIOUS_TAG }}" \
        --var-end "${{ env.TARGET_TAG }}" \
        examples/api-doc-gen/recipes/release-notes.ts
```

### 3.2.4 Run Interaction

Once the Memory Pack is uploaded to the Google Cloud, you can use it to run the Interaction to generate the release notes. Here is an example:

```yaml
- name: Generate release notes
  run: |
    mappings=$(cat << EOF
    {
        "@memory": "release-notes/${GITHUB_RUN_ID}-${GITHUB_SHA::7}",
        "@": "@",
        "lang": "${lang}",
        "issues": "@content:issues/*",
        "pull_requests": "@content:pull_requests/*",
        "code_diff": "@content:range_diff.txt",
        "commits": "@content:commits.txt"
    }
    EOF
    )

    vertesia run GenerateReleaseNotes -d "$mappings"
```

The generated content will be printed in the GitHub workflow. It's up to you to handle the logic. For example, you may want to store the result into a file, and then create a pull-request to a human review before publishing it. Or you may want to store the generated result directly as part of the "release" on top of an existing tag on GitHub.

### 3.3 Recap

In this section, we saw how to generate the API key from Vertesia UI. Then, we saw how to use `vertesia` CLI to build the Memory Pack and run the Interaction to generate the content in GitHub Actions.

## Conclusion

Thanks for spending time to read the whole tutorial! In this page, we went through the 3 parts of the release notes generation, including the data collection, document generation, and the integration to GitHub Actions. We saw how to create a small data pipeline to collect information using Recipe, a standalone TypeScript file with Memory Commands. Then, we saw how to build the Memory Pack using the `vertesia` command. We saw how to create an Interaction and run it using the `vertesia` CLI with a Memory Pack. Finally, we saw how to generate the API key from Vertesia UI. Then, we saw how to integrate the whole process into GitHub Actions.

---

## Workflow Activities

Source: https://docs.vertesiahq.com/workflows/activities-catalog
Markdown: https://docs.vertesiahq.com/llms/workflows/activities-catalog.md

This page lists the public workflow activities that are available in the platform.

---

## Configuration

Source: https://docs.vertesiahq.com/workflows/configuration
Markdown: https://docs.vertesiahq.com/llms/workflows/configuration.md

First, let's go over a few concepts:

**Workflow DSL:** the workflow DSL is a JSON-based language that is used to define workflows. It is a simple language that is easy to learn and use. The DSL is composed of a list of steps. Each step can be either an activity or a child workflow.

**Activities:** Activities are the building blocks of workflows. They are the individual tasks that are executed by the workflow worker. Details about the Workflow Activities are in the [Workflow Activities](/activities_catalog) section.

**Child Workflows:** Child workflows are workflows that are executed as part of another workflow. They are useful for breaking down complex workflows into smaller, more manageable units.

## Prerequisites

In order to easily create and update Workflow definitions in Vertesia, you will need to use the Vertesia CLI. If you haven't installed or configured it yet, please have a look at the [documentation](../quickstart#vertesia-cli)

## Workflow Definition

A workflow definition is a JSON structures with contains at least the following:

- a name
- a description
- an array of steps
- input variables

Below is an example of intake workflow that can be triggered when a new text document is uploaded to vertesia.

```json
{
  "name": "MyWorkflow",
  "description": "This is my workflow.",
  "vars": {
    "interactionsNames": {
      "extractInformation": "sys:ExtractInformation",
      "selectDocumentType": "sys:SelectDocumentType",
      "generateMetadataModel": "sys:GenerateMetadataModel",
      "chunkDocument": "sys:ChunkDocument"
    }
  },
  "steps": [
    {
      "name": "setDocumentStatus",
      "params": {
        "status": "processing"
      }
    },
    {
      "title": "Extract text from the current document",
      "name": "generateObjectText",
      "type": "workflow",
      "output": "extractResult"
    },
    {
      "title": "Generate or assign a content type for the current document",
      "name": "generateOrAssignContentType",
      "import": ["interactionsNames"],
      "params": {
        "interactionNames": {
          "generateMetadataModel": "${interactionsNames.generateMetadataModel}",
          "selectDocumentType": "${interactionsNames.selectDocumentType}"
        }
      },
      "condition": {
        "extractResult.hasText": {
          "$eq": true
        }
      }
    },
    {
      "title": "Generate document properties from text content",
      "name": "generateDocumentProperties",
      "import": ["interactionsNames"],
      "params": {
        "interactionName": "${interactionsNames.extractInformation}"
      },
      "condition": {
        "extractResult.hasText": {
          "$eq": true
        }
      }
    },
    {
      "title": "Chunk the current document text",
      "name": "chunkDocument",
      "import": ["interactionsNames"],
      "params": {
        "interactionName": "${interactionsNames.chunkDocument}",
        "createParts": true
    },
      "condition": {
        "extractResult.hasText": {
        "$eq": true
        }
    }
    },
    {
      "name": "generateEmbeddings",
      "title": "Generate embeddings for text",
      "params": {
        "type": "text",
        "force": false
      }
    },
    {
      "name": "setDocumentStatus",
      "params": {
        "status": "completed"
      }
    }
  ]
}
```

## Workflow Variables

The DSL supports variables that can be used to store data and pass it between steps. Variables are defined in the `vars` property of the workflow. The value of a variable can be a literal value or a reference to another variable. References to variables are enclosed in `${}`. For example, the following DSL defines a variable named `myVariable` with the value "Hello World!":

```json
{
  "vars": {
    "myVariable": "Hello World!"
  }
}
```

The value of `myVariable` can then be referenced in other parts of the DSL using `${myVariable}`. For example, the following DSL logs the value of `myVariable` to the console:

```json
{
  "steps": [
    {
      "type": "activity",
      "name": "log",
      "params": {
        "message": "The value of myVariable is: ${myVariable}"
      }
    }
  ]
}
```

## Conditions

The DSL supports conditions that can be used to control the flow of the workflow. Conditions are defined in the `condition` property of a step. The value of a condition is a JSON object that describes the condition. The following operators are supported:

| Operator | Description |
|---|---|
| `$eq` | Equal to |
| `$ne` | Not equal to |
| `$gt` | Greater than |
| `$gte` | Greater than or equal to |
| `$lt` | Less than |
| `$lte` | Less than or equal to |
| `$in` | In array |
| `$nin` | Not in array |
| `$regexp` | Matches regular expression |

For example, the following DSL defines a step that only executes if the value of the variable `myVariable` is equal to "Hello World!":

```json
{
  "steps": [
    {
      "type": "activity",
      "name": "log",
      "condition": {
        "$eq": {
          "myVariable": "Hello World!"
        }
      },
      "params": {
        "message": "The value of myVariable is: ${myVariable}"
      }
    }
  ]
}
```

## Fetch

The DSL supports fetching data from external sources during the workflow execution. The `fetch` property of a step is used to define the data to fetch. The value of the `fetch` property is a JSON object that describes the data to fetch. The following properties are supported:

| Property | Description |
|---|---|
| `type` | The type of data to fetch. |
| `source` | The source of the data. |
| `query` | The query to use to fetch the data. |
| `select` | The fields to select from the fetched data. |
| `limit` | The maximum number of results to fetch. |
| `on_not_found` | How to handle not found objects. |

For example, the following DSL defines a step that fetches a document from the store:

```json
{
  "steps": [
    {
      "type": "activity",
      "name": "fetchDocument",
      "fetch": {
        "type": "document",
        "query": {
          "id": "${documentId}"
        }
      },
      "output": "document"
    }
  ]
}
```

## Projection

The DSL supports projecting data from the result of an activity. The `projection` property of a step is used to define the data to project. The value of the `projection` property is a JSON object that describes the data to project. The following operators are supported:

| Operator | Description |
|---|---|
| `$include` | Include the specified fields. |
| `$exclude` | Exclude the specified fields. |

For example, the following DSL defines a step that projects the `name` and `description` fields from the result of the `fetchDocument` activity:

```json
{
  "steps": [
    {
      "type": "activity",
      "name": "fetchDocument",
      "fetch": {
        "type": "document",
        "query": {
          "id": "${documentId}"
        }
      },
      "output": "document"
    },
    {
      "type": "activity",
      "name": "projectDocument",
      "params": {
        "document": "${document}"
      },
      "projection": {
        "$include": [
          "name",
          "description"
        ]
      },
      "output": "projectedDocument"
    }
  ]
}
```

---

## Workflows for Agentic AI

Source: https://docs.vertesiahq.com/workflows/overview
Markdown: https://docs.vertesiahq.com/llms/workflows/overview.md

Vertesia provides unparalleled reliability and resilience for your generative AI and agentic AI pipelines. It ensures that even complex, multi-step workflows, such as those executed by agents involving chained prompts, tool use, and iterative refinement, complete successfully. Vertesia automatically handles failures, retries, and state persistence, allowing developers to focus on optimizing AI models and agent logic rather than on implementing robust error handling or recovery mechanisms.

## Key Benefits

**Durable AI Orchestration:** Vertesia supports long-running AI workflows, from minutes to days. It preserves the state of generative processes and agentic decisions, ensuring reliable resumption after system failures or network interruptions. This capability is critical for multi-stage content generation, complex agentic explorations, and other extended AI tasks.

**Simplified AI Pipeline Development:** Vertesia abstracts the complexities of distributed computing, enabling the design and execution of sophisticated Agentic AI workflows using straightforward code. This reduces development overhead associated with managing AI agent state or coordinating complex generative processes.

**Full Observability of AI Execution:** Vertesia provides comprehensive visibility into generative AI and agentic workflows. Built-in tools and a detailed event history offer real-time status updates and complete execution traces for each AI task. This facilitates debugging agent behavior, auditing generative outputs, and understanding overall AI pipeline flow.

**Scalable AI Workloads:** Designed for horizontal scaling, Vertesia efficiently manages thousands to millions of concurrent AI workflow executions, supporting both complex agent simulations and large-scale content generation.

## Configuration Options

Vertesia offers two methods for configuring workflows, accommodating varying levels of complexity and customization:

**JSON DSL for Rapid Configuration:** For straightforward generative AI tasks or basic agentic sequences, a JSON Domain-Specific Language (DSL) is available. This allows developers to define workflow logic, model parameters, and conditional execution directly within a human-readable JSON format. This option is ideal for rapid iteration on prompt engineering, simple agent decision trees, or orchestrating direct model calls without requiring custom code.

**Code-Based Workflows with Custom Docker Images:** For advanced generative AI or agentic logic requiring deep customization, integration with external systems, or specialized libraries, Vertesia supports code-based workflows. Developers can implement workflow logic using the Typescript SDK and package it within a custom Docker image. This provides full control over the execution environment, dependencies, and runtime, enabling the deployment of highly sophisticated AI agents, advanced generative pipelines with custom pre/post-processing, or niche AI frameworks directly within the Vertesia workflow execution environment.

---

## Workflow DSL

Source: https://docs.vertesiahq.com/workflows/workflow-dsl
Markdown: https://docs.vertesiahq.com/llms/workflows/workflow-dsl.md

The Vertesia Platform uses a DSL to define workflows. The DSL is a JSON object that describes the steps of the workflow. Each step can be either an activity or a child workflow.

## Activities

The following activities are available:

| Activity Name | Description | Parameters | Output |
|---|---|---|---|
| **extractText** | Extracts text from a document. | `document`: The document to extract text from. | `text`: The extracted text. |
| **generateText** | Generates text using a prompt template. | `prompt`: The prompt template to use. `data`: The data to pass to the prompt template. | `text`: The generated text. |
| **translateText** | Translates text from one language to another. | `text`: The text to translate. `targetLanguage`: The target language. | `translatedText`: The translated text. |
| **summarizeText** | Summarizes text. | `text`: The text to summarize. | `summary`: The summarized text. |
| **analyzeSentiment** | Analyzes the sentiment of text. | `text`: The text to analyze. | `sentiment`: The sentiment of the text. |
| **classifyText** | Classifies text into categories. | `text`: The text to classify. `categories`: The categories to classify into. | `category`: The category of the text. |
| **extractEntities** | Extracts entities from text. | `text`: The text to extract entities from. | `entities`: The extracted entities. |
| **generateEmbeddings** | Generates embeddings for text. | `text`: The text to generate embeddings for. | `embeddings`: The generated embeddings. |
| **searchEmbeddings** | Searches for similar text using embeddings. | `embeddings`: The embeddings to search for. | `results`: The search results. |
| **createDocument** | Creates a new document. | `type`: The type of the document. `properties`: The properties of the document. | `document`: The created document. |
| **updateDocument** | Updates an existing document. | `document`: The document to update. `properties`: The properties to update. | `document`: The updated document. |
| **deleteDocument** | Deletes an existing document. | `document`: The document to delete. | |
| **executeInteraction** | Executes an existing interaction. | `interaction`: The interaction to execute. `data`: The data to pass to the interaction. | `result`: The result of the interaction. |
| **runJavascriptCode** | Runs JavaScript code. | `code`: The JavaScript code to run. | `result`: The result of the code execution. |
| **sleep** | Pauses the workflow for a specified amount of time. | `duration`: The duration to pause for. | |
| **log** | Logs a message to the console. | `message`: The message to log. | |

### Child Workflows

Child workflows are used to execute another workflow as a step in the current workflow. The `name` property specifies the endpoint of the child workflow to execute. The `async` property specifies whether or not to wait for the child workflow to finish before continuing the current workflow.

### Example DSL Workflow

```json
{
  "name": "My Workflow",
  "description": "This is my workflow.",
  "vars": {
    "myVariable": "Hello World!"
  },
  "steps": [
    {
      "type": "activity",
      "name": "log",
      "params": {
        "message": "The value of myVariable is: ${myVariable}"
      }
    },
    {
      "type": "workflow",
      "name": "My Child Workflow",
      "async": true,
      "output": "childWorkflowResult"
    }
  ]
}
```

### DSL Variables

The DSL supports variables that can be used to store data and pass it between steps. Variables are defined in the `vars` property of the workflow. The value of a variable can be a literal value or a reference to another variable. References to variables are enclosed in `${}`. For example, the following DSL defines a variable named `myVariable` with the value "Hello World!":

```json
{
  "vars": {
    "myVariable": "Hello World!"
  }
}
```

The value of `myVariable` can then be referenced in other parts of the DSL using `${myVariable}`. For example, the following DSL logs the value of `myVariable` to the console:

```json
{
  "steps": [
    {
      "type": "activity",
      "name": "log",
      "params": {
        "message": "The value of myVariable is: ${myVariable}"
      }
    }
  ]
}
```

### DSL Conditions

The DSL supports conditions that can be used to control the flow of the workflow. Conditions are defined in the `condition` property of a step. The value of a condition is a JSON object that describes the condition. The following operators are supported:

| Operator | Description |
|---|---|
| `$eq` | Equal to |
| `$ne` | Not equal to |
| `$gt` | Greater than |
| `$gte` | Greater than or equal to |
| `$lt` | Less than |
| `$lte` | Less than or equal to |
| `$in` | In array |
| `$nin` | Not in array |
| `$regexp` | Matches regular expression |

For example, the following DSL defines a step that only executes if the value of the variable `myVariable` is equal to "Hello World!":

```json
{
  "steps": [
    {
      "type": "activity",
      "name": "log",
      "condition": {
        "$eq": {
          "myVariable": "Hello World!"
        }
      },
      "params": {
        "message": "The value of myVariable is: ${myVariable}"
      }
    }
  ]
}
```

### DSL Fetch

The DSL supports fetching data from external sources during the workflow execution. The `fetch` property of a step is used to define the data to fetch. The value of the `fetch` property is a JSON object that describes the data to fetch. The following properties are supported:

| Property | Description |
|---|---|
| `type` | The type of data to fetch. |
| `source` | The source of the data. |
| `query` | The query to use to fetch the data. |
| `select` | The fields to select from the fetched data. |
| `limit` | The maximum number of results to fetch. |
| `on_not_found` | How to handle not found objects. |

For example, the following DSL defines a step that fetches a document from the store:

```json
{
  "steps": [
    {
      "type": "activity",
      "name": "fetchDocument",
      "fetch": {
        "type": "document",
        "query": {
          "id": "${documentId}"
        }
      },
      "output": "document"
    }
  ]
}
```

### DSL Projection

The DSL supports projecting data from the result of an activity. The `projection` property of a step is used to define the data to project. The value of the `projection` property is a JSON object that describes the data to project. The following operators are supported:

| Operator | Description |
|---|---|
| `$include` | Include the specified fields. |
| `$exclude` | Exclude the specified fields. |

For example, the following DSL defines a step that projects the `name` and `description` fields from the result of the `fetchDocument` activity:

```json
{
  "steps": [
    {
      "type": "activity",
      "name": "fetchDocument",
      "fetch": {
        "type": "document",
        "query": {
          "id": "${documentId}"
        }
      },
      "output": "document"
    },
    {
      "type": "activity",
      "name": "projectDocument",
      "params": {
        "document": "${document}"
      },
      "projection": {
        "$include": [
          "name",
          "description"
        ]
      },
      "output": "projectedDocument"
    }
  ]
}
```