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

# Integrating AI agents with Flows

> Step-by-step guide to integrating Cognite Data Fusion (CDF) AI agent support with Cognite Flows custom apps using @cognite/app-sdk.

This guide is for developers building **Flows** custom apps in **Cognite Data Fusion (CDF)**. You will connect your app to the built-in CDF agent: expose app state as resources, register actions the agent can call, and send messages from your UI. Complete the [prerequisites](#prerequisites) before you follow the integration steps.

With `@cognite/app-sdk@0.3.1` or later, you can:

* **Let the agent see your Flows custom app.** Expose the app's content and state as resources the agent can add to its context.
* **Let the agent interact with your Flows custom app.** Register actions as tools the agent can use to perform tasks in your app on the user's behalf.
* **Send messages from your Flows custom app to the agent.** Trigger context-aware prompts when the user clicks a button or reaches a key point in a workflow.

## Prerequisites

Before you start, ensure you have:

* Access to **Cognite Data Fusion (CDF)** with appropriate capabilities. See [Assign capabilities](/cdf/access/guides/capabilities#flows-custom-apps) for more information.

* A **Flows** development environment set up. See [Get started with Flows](/cdf/flows/guides/getting-started).

* `@cognite/app-sdk@0.3.1` or later installed. This version adds the agent panel, messaging, and server registration APIs used in this guide.

  ```bash theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
  npm install @cognite/app-sdk
  ```

* A `HostAppAPI` instance from `connectToHostApp` on mount:

  ```typescript wrap theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
  import { connectToHostApp } from '@cognite/app-sdk';

  const { api } = await connectToHostApp({ applicationName: 'my-app' });
  ```

<Warning>
  `connectToHostApp` only works when your Flows app runs **inside CDF** (embedded in a CDF page with a parent host window). If you open only the Vite dev URL, for example, `https://localhost:3001`, without loading the app through CDF, there is no host to connect to and the promise rejects.

  Wrap the call in `try`/`catch`. When connect fails, skip `registerAgentServer` and other agent APIs, and hide or disable buttons that open the agent panel or send messages. See [Development and production behavior](#development-and-production-behavior) for an example.
</Warning>

## Integrate the agent

Use your `HostAppAPI` instance to control the agent panel, send messages from your app, and register an agent server with the resources and actions the agent can use.

<Steps>
  <Step title="Open the agent panel">
    Call `api.sendAgentLayoutMode` to change the visibility or layout of the agent panel.

    ```typescript theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
    await api.sendAgentLayoutMode({ mode: 'sidebar' });
    ```

    **AgentLayoutPayload options:**

    | **Field**         | **Type**                                | **Description**                                        |
    | ----------------- | --------------------------------------- | ------------------------------------------------------ |
    | `mode`            | `'sidebar' \| 'fullscreen' \| 'closed'` | Target panel layout.                                   |
    | `conversationId`  | `string?`                               | Resume a specific existing conversation.               |
    | `agentExternalId` | `string?`                               | Target a specific registered agent.                    |
    | `source`          | `AgentSource?`                          | Surface identifier; omit unless required by your host. |

    <Tip>`{ mode: 'sidebar' }` is the most common call, typically wired to a persistent toolbar button that opens the assistant at any time.</Tip>
  </Step>

  <Step title="Send the agent a message">
    Call `api.sendAgentMessage` to inject text into the agent's chat, optionally starting a fresh session. Use `sendAgentMessage` to pre-populate the agent with context when the user clicks a contextual trigger in your app.

    ```typescript wrap theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
    await api.sendAgentMessage({
      message: 'Summarize the current state and highlight any risks.',
      newSession: true,
    });
    ```

    **AgentMessagePayload options:**

    | **Field**    | **Type**   | **Description**                                   |
    | ------------ | ---------- | ------------------------------------------------- |
    | `message`    | `string`   | Text to inject into the chat.                     |
    | `newSession` | `boolean?` | Clear the existing conversation before injecting. |

    <Note>Call `sendAgentLayoutMode` before `sendAgentMessage`. The agent panel must be open for the user to see the injected message.</Note>

    ```typescript wrap theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
    await api.sendAgentLayoutMode({ mode: 'sidebar' });
    await api.sendAgentMessage({
      message: `Analyze the schedule for "${tarName}" and suggest how to reduce the total duration.`,
      newSession: true,
    });
    ```

    Use `newSession: true` for contextual entry points where the user is starting a new task. Omit it when continuing an existing conversation.
  </Step>

  <Step title="Register the agent server">
    An agent server exposes resources (read-only context the agent can request) and actions (tools the agent can call). Register it once on mount, and unregister it on unmount.

    ```typescript wrap theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
    import { createAgentServer, registerAgentServer } from '@cognite/app-sdk';

    const server = createAgentServer({
      uri: 'my-app',           // namespaced by CDF with the instance ID
      actions: [...],
      resources: [...],
    });

    await registerAgentServer(api, server);

    // On unmount:
    await api.unregisterAgentServer('my-app');
    ```

    In React, manage the lifecycle with `useEffect`:

    ```typescript wrap theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
    useEffect(() => {
      if (!api) return;

      const server = createAgentServer({ uri: 'my-app', actions, resources });
      void registerAgentServer(api, server);

      return () => {
        void api.unregisterAgentServer('my-app');
      };
    }, [api]);
    ```
  </Step>

  <Step title="Define resources">
    Resources are read-only data the agent reads to understand the current app state. Each resource has a URI, a name, and a `read` function that returns content.

    ```typescript wrap theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
    import { createAgentResource } from '@cognite/app-sdk';

    createAgentResource({
      uri: 'my-app://current-state',
      name: 'Current application state',
      description: 'Describes what data the agent will receive and when it should read this resource.',
      async read() {
        const data = getStateFromSomewhere();
        return [{ type: 'json', data }];
      },
    })
    ```

    The `read` function returns an array of content parts. Each element is one of:

    * `{ type: 'json', data: unknown }` — structured data. The agent reasons over JSON directly; prefer this format when you can.
    * `{ type: 'text', text: string }` — free-form text.

    The `description` field is visible to the agent. Use it to explain what the resource contains and when to read it. Treat it like a function docstring.

    ### Example: TAR planner resources

    The TAR planner application exposes two resources. Both are populated from `TARStorageService` (localStorage) and the critical path algorithm, giving the agent a live view of what the user is managing.

    ```typescript wrap theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
    createAgentResource({
      uri: 'tar-planner://current-tar',
      name: 'Current TARs',
      description: 'All TARs with name, facility, scope, dates, status, and work order count.',
      async read() {
        return [{ type: 'json', data: storage.getAllTars() }];
      },
    });

    createAgentResource({
      uri: 'tar-planner://schedule-summary',
      name: 'Schedule summary',
      description: 'Per-TAR computed schedule with critical path flags and float.',
      async read() {
        return [{ type: 'json', data: computeScheduleSummary(storage) }];
      },
    });
    ```
  </Step>

  <Step title="Define actions">
    Actions are tools the agent can invoke. They can be read-only queries or mutating operations. Define parameters with a Zod schema. The `.describe()` call on each field is the agent's documentation for that parameter.

    ```typescript wrap theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
    import { createAgentAction } from '@cognite/app-sdk';
    import { z } from 'zod';

    createAgentAction({
      name: 'get_item_details',           // snake_case; shown to the agent as the tool name
      description: 'What this does, what it returns, and when to call it.',
      parameters: z.object({
        item_id: z.string().describe('The ID of the item to retrieve'),
        include_history: z.boolean().optional().describe('Whether to include audit history'),
      }),
      async handler({ item_id, include_history }) {
        const item = await myService.getItem(item_id, { history: include_history });
        return {
          content: [{ type: 'json', data: item }],
        };
      },
    })
    ```

    ### Mutating actions

    Mutating actions can write to your app's state.

    <Warning>
      The agent does not currently confirm with the user before calling mutating actions. Use mutating actions with caution and make the agent's responsibility explicit in the action's `description`.
    </Warning>

    ```typescript wrap theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
    createAgentAction({
      name: 'add_work_order_to_tar',
      description:
        'Add a maintenance order to a TAR. Call this ONLY when the user has explicitly approved. The app UI updates immediately.',
      parameters: z.object({
        tar_id: z.string().describe('The TAR to add the work order to'),
        maintenance_order_external_id: z.string().describe('The externalId of the order'),
      }),
      async handler({ tar_id, maintenance_order_external_id }) {
        storage.saveEntry({ tarId: tar_id, maintenanceOrderExternalId: maintenance_order_external_id, ... });
        return { content: [{ type: 'json', data: { success: true } }] };
      },
    })
    ```

    After a mutating action writes to your state layer, the UI updates on the next render cycle if your components read from the same state. No extra signaling is required.
  </Step>
</Steps>

## Verify integration

Load your Flows custom app **inside CDF** (through the CDF development URL, not only `https://localhost:3001`). Confirm the following:

1. Open the agent panel from your UI control or a `sendAgentLayoutMode` call.
2. Send a test message and confirm it appears in the agent chat.
3. Ask the agent a question that should use a registered resource and confirm the answer reflects your app state.
4. If you registered mutating actions, test them only after you understand the risks in [Mutating actions](#mutating-actions).

<Check>
  When integration works, the agent panel opens from your app, messages appear in chat, and resource-backed answers match the state your app exposes.
</Check>

## Recommended project structure

Separate the registration lifecycle from the action and resource definitions to test both independently.

```text wrap theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
src/features/agent/
  agentActions.ts     — pure factory: (deps) => Action[]
  agentResources.ts   — pure factory: (deps) => Resource[]
  useAgentServer.ts   — useEffect lifecycle hook; calls the factories and registers
```

Inject services as parameters rather than importing them directly. That approach lets you unit test actions and resources with mock services.

```typescript wrap theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
// agentActions.ts
export function buildAgentActions(
  storage: StorageService,
  dataService: DataService,
): AgentActions {
  return [
    createAgentAction({ ..., async handler({ id }) { return storage.get(id); } }),
  ];
}

// useAgentServer.ts
export function useAgentServer(api: HostAppAPI | null): void {
  const storage = useStorageService();    // injected via React context
  const dataService = useDataService();

  useEffect(() => {
    if (!api) return;
    const server = createAgentServer({
      uri: 'my-app',
      actions: buildAgentActions(storage, dataService),
      resources: buildAgentResources(storage, dataService),
    });
    void registerAgentServer(api, server);
    return () => { void api.unregisterAgentServer('my-app'); };
  }, [api, storage, dataService]);
}
```

To test `buildAgentActions`, pass mock service implementations and call `handler` directly without mounting the hook.

## Development and production behavior

`connectToHostApp` behaves differently depending on where your app runs:

| **Environment**                     | **`connectToHostApp` behavior** | **Effect**                                                  |
| ----------------------------------- | ------------------------------- | ----------------------------------------------------------- |
| Inside CDF                          | Resolves with `{ api }`         | Full agent integration functionality.                       |
| Standalone development (`vite dev`) | Rejects                         | Agent features disabled; hide or disable agent UI triggers. |

Use the following pattern to handle both environments: catch rejections from `connectToHostApp` and disable agent features when your app isn't running inside CDF.

```typescript wrap theme={"languages":{"custom":["/_languages/kuiper.json","../_languages/kuiper.json"]}}
let api: HostAppAPI | null = null;
try {
  ({ api } = await connectToHostApp({ applicationName: 'my-app' }));
} catch {
  // Not running inside CDF: agent features disabled
}
```

In React, hold `api` in state or context. When the host connection succeeds, your registration effect runs and UI components can show or hide agent triggers.

## Further reading

* [Get started with Flows](/cdf/flows/guides/getting-started)
* [Flows authentication](/cdf/flows/reference/api/auth)
* [Atlas AI agent building](/cdf/atlas_ai/guides/atlas_ai_agent_building)
* [CDF SDK documentation](/dev/sdks/)
