> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ravenna.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Build Foundry functions

> Create, test, and publish Foundry functions to add custom code actions to Ravenna workflows and AI agents, with built-in editor and runtime context.

A function is something Foundry can do, like "send a DocuSign envelope" or "look up a Salesforce contact." You build it by describing what you want, and Foundry writes and tests the code for you.

Functions live inside the Foundry app (in the workspace sidebar between **Agents** and **Workflows**). They use integrations created by your org admins in [Settings → Integrations](/documentation/automate/foundry/integrations).

***

## The workspace

Open Foundry from the sidebar. The left panel has two tabs:

* **Functions**. Every function in your workspace, with their last test status (Passing, Failing, or Never run). Click **New Function** to start a new one.
* **Integrations**. Every integration available to Foundry. The **New Integration** button takes you to Settings.

When you open a function, the right side becomes the **function workspace** with the generated code and a publish button. The panel on the left shifts into four tabs for that function:

* **Chat**. The conversation Foundry uses to generate and refine code.
* **Test**. Run the function with sample inputs.
* **Integrations**. Connect or disconnect integrations for this function.
* **Configure**. Edit the name, description, and AI tool prompt, or delete the function.

***

## Build a function

<Steps>
  <Step title="Create the function">
    In Foundry's **Functions** tab, click **New Function**. A blank function opens in the workspace.
  </Step>

  <Step title="Connect integrations">
    Open the function's **Integrations** tab and connect the integration the function should call. You can add more than one, which is useful when a function needs to read from one tool and write to another. The first integration is the **primary** one and supplies the function's default scope and OAuth connected account.

    If you don't have the integration you need, an admin can create one in [Settings → Integrations](/documentation/automate/foundry/integrations).
  </Step>

  <Step title="Describe what it should do">
    In the **Chat** tab, write what you want the function to do in plain language. Be specific:

    <Prompt description="List all active users and return their name, email, and role">
      List all active users and return their name, email, and role
    </Prompt>

    <Prompt description="Create a project with the given name, description, and team">
      Create a project with the given name, description, and team
    </Prompt>

    <Prompt description="Look up a contact by email and return their company and last activity date">
      Look up a contact by email and return their company and last activity date
    </Prompt>

    Foundry generates code, type-checks it, and runs a dry-run against the API.
  </Step>

  <Step title="Test with real inputs">
    Open the **Test** tab, fill in the inputs, pick a runtime environment (development, staging, or production), and click **Run Test**. Foundry shows the result, every HTTP request the function made, and any log output.

    Turn on **Dry Run** in the function workspace header to test without changing external state. See [Dry Run mode](#dry-run-mode) below.
  </Step>

  <Step title="Refine in chat">
    If something isn't right, ask in plain language from the **Chat** tab:

    <Prompt description="Add pagination so it fetches every page.">
      Add pagination so it fetches every page.
    </Prompt>

    <Prompt description="If the API returns a 429, wait and retry up to 3 times.">
      If the API returns a 429, wait and retry up to 3 times.
    </Prompt>

    <Prompt description="Only return users where status is active.">
      Only return users where status is active.
    </Prompt>
  </Step>

  <Step title="Publish">
    Click **Publish** in the function workspace. Foundry validates the code one more time, generates an AI tool prompt (so agents know when to call it), and publishes the function. It's now available as a workflow step and as a tool for your AI agents.
  </Step>
</Steps>

<Info>
  Foundry shows generation status (`Idle`, `In progress`, `Success`, or `Error`) in the function workspace, so if a generation stalls or fails, you can see why.
</Info>

<Callout icon="sparkles" color="#7C3AED">
  Want prompts you can copy? See [Foundry examples](/guides/how-to/foundry/examples) for worked recipes, or [Tips & troubleshooting](/guides/how-to/foundry/tips-and-troubleshooting) for ways to get better code out of Foundry.
</Callout>

***

## Dry Run mode

Dry Run lets you exercise a function end-to-end without changing external state. Use it to safely test functions that send email, create tickets, post messages, or update records. Turn on the **Dry Run** toggle in the function workspace header, then click **Run Test** from the **Test** tab as usual.

When you publish a function, Foundry analyzes the code and picks one of two strategies. The active strategy shows up in the toggle's tooltip:

| Strategy | When Foundry picks it                                                                     | What runs                                                                                                                                                   |
| -------- | ----------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| As-is    | The function only reads or searches (no external writes).                                 | The original code runs live.                                                                                                                                |
| Variant  | The function makes at least one state-changing call (create, update, delete, send, etc.). | Foundry runs a generated copy of the code where only the writes are mocked. Reads stay live, so bad inputs and auth errors surface exactly like a real run. |

A few things to know:

* Dry Run is available once a function has been published or had its dry-run code generated. New, never-published functions show the toggle disabled.
* The code editor switches to read-only while Dry Run is on, and a banner tells you which strategy is active. Turn Dry Run off before editing.
* Each mocked write is logged in the test output so you can see exactly what the function *would* have done.
* If you change the function and the dry-run variant looks stale, click the **Sparkles** button next to the toggle to regenerate it. Only workspace admins can regenerate dry-run code.
* `dryRun` is also exposed on the `POST /foundry/actions/run-test` API via the `useDryRunVariant` flag.

<Callout icon="flask-conical" color="#D97706">
  Dry Run is the safest way to test a function against production credentials. Use it whenever a failed test would create unwanted side effects, like duplicate tickets or real customer notifications.
</Callout>

***

## Advanced: the ActionContext SDK

This section is for users who want to read or hand-edit the generated TypeScript code. You don't need any of this to build, test, or publish a function.

Every function exports a `run` function that receives an `ActionContext`. The context gives you authenticated HTTP clients (one per integration), the Ravenna operations namespace, and runtime info.

```typescript theme={"system"}
import type { ActionContext } from '@ravenna/actions'

export async function run(ctx: ActionContext, input: RunInput): Promise<RunOutput> {
  // Each integration is available by its slug, with auth and default headers injected.
  const salesforce = ctx.integrations.salesforce
  const response = await salesforce.fetch(`${salesforce.baseUrl}/services/data/v59.0/query`)
  const { records } = await response.json()

  // Call Ravenna operations the same way you'd call any built-in action.
  await ctx.ravenna.createTicket({
    title: `New account: ${records[0].Name}`,
    queueId: input.queueId,
    statusId: input.statusId,
    requesterId: input.requesterId,
    authorId: input.authorId,
  })

  ctx.log(`Processed ${records.length} records in ${ctx.env}`)

  return { count: records.length }
}
```

### Context properties

| Property                                 | What it is                                                                                    |
| ---------------------------------------- | --------------------------------------------------------------------------------------------- |
| `ctx.integrations`                       | A map of integration clients keyed by slug. Each has `.fetch` (auth-injected) and `.baseUrl`. |
| `ctx.ravenna`                            | The Ravenna operations namespace. See the list below.                                         |
| `ctx.log()`                              | Writes to the function's log output.                                                          |
| `ctx.workspaceId` / `ctx.organizationId` | IDs for the current execution context.                                                        |
| `ctx.env`                                | `'development'`, `'staging'`, or `'production'`.                                              |
| `ctx.workflowExecutionId`                | Set when the function is running as a workflow step.                                          |

<Info>
  `ctx.fetch`, `ctx.baseUrl`, and `ctx.auth` still exist for backwards compatibility but are deprecated. They point at the primary integration. New code should use `ctx.integrations.<slug>` so it keeps working when more integrations are added to the function.
</Info>

### Ravenna operations

Available on `ctx.ravenna`:

**Tickets:** `createTicket`, `updateTicket`, `addTicketComment`, `setTicketStatus`, `setTicketPriority`, `setTicketAssignee`, `addTicketFollowers`, `addTicketTags`, `moveTicket`

**Users:** `getUser`

Slack is not a `ctx.ravenna` operation. To post to Slack from an action, select the Slack integration and call the Slack Web API directly with `ctx.integrations.native_slack.fetch`. Your workspace's bot token is attached automatically, so you pass Slack arguments only (such as channel and text), never a workspace or team id.

<Note>
  Foundry actions for Microsoft Teams are not yet available. See the [Microsoft Teams integration overview](/integrations/microsoft-teams/overview) for the current beta scope.
</Note>

***

## Advanced: how generation and testing work

Foundry validates generated code in two phases, automatically:

1. **TypeScript compilation** catches syntax errors, type mismatches, and bad imports. Foundry auto-fixes failures up to 3 times.
2. **Dry-run execution** runs the code in a sandbox without making real API calls, to catch issues like malformed URLs. Foundry auto-fixes failures up to 2 times.

When you run a real test from the **Test** tab, you pick the runtime environment (`development`, `staging`, or `production`) that the function sees via `ctx.env`. The function runs in an isolated sandbox with the credentials resolved for each of its integrations, limited to 30 seconds per execution.

When you publish, Foundry runs a final validation, then generates an AI tool prompt that helps agents decide when to call the function. You can regenerate that prompt from the function's **Configure** tab.

<Callout icon="link" color="#6B7280">
  Learn how to [use published functions](/documentation/automate/foundry/using-actions) in workflows and with AI agents.
</Callout>
