integrate-fusion-agent
cognitedata/builder-skills
Integrate your Flows/Dune app with Fusion's built-in PAIA agent panel using @cognite/app-sdk.
What is integrate-fusion-agent?
Wires a Flows/Dune application into the Fusion built-in PAIA agent, enabling you to open the agent panel, send contextual messages, and expose app state and actions. Use this skill whenever you need to integrate agent capabilities into your app rather than writing integration code manually.
- Open the agent panel as a sidebar or fullscreen UI from your app
- Send contextual messages to the agent (e.g., on item click) with relevant app context
- Register an agent server to expose app state as readable resources
- Expose app actions as tools the agent can invoke
- Handle graceful fallback when running outside Fusion environment
- Set up correct lifecycle management and recommended file structure
How to install integrate-fusion-agent
npx skills add https://github.com/cognitedata/builder-skills --skill integrate-fusion-agent- @cognite/app-sdk version 0.3.1 or higher
- React app (or equivalent framework with hooks/lifecycle support)
- Running within Fusion environment (gracefully degrades outside)
How to use integrate-fusion-agent
- 1.Read package.json and src/App.tsx to understand the current app structure
- 2.Install @cognite/app-sdk if not already present using your package manager
- 3.Create a useHostApp hook to connect to the host app on mount and store the HostAppAPI instance
- 4.Wire a button to api.sendAgentLayoutMode({ mode: 'sidebar' }) to open the agent panel
- 5.Use api.sendAgentMessage() on contextual triggers (e.g., item click) to inject context into the agent
- 6.Create agentResources.ts with createAgentResource factories to expose app state the agent can read
- 7.Create agentActions.ts with createAgentAction factories to expose tools the agent can invoke
- 8.Create useAgentServer.ts lifecycle hook to register and unregister resources/actions on mount/unmount
Use cases
- Add an 'Open Assistant' button to analyze selected items in your app
- Inject context into the agent chat when a user clicks 'Analyse this item'
- Expose your app's current state (filtered items, statuses) so the agent can answer questions about what the user is viewing
- Let the agent call actions like updating item status or retrieving full details
- Build a sidebar agent panel integrated with your Flows/Dune application
- Flows/Dune app developers
- Teams building Fusion applications with agent capabilities
- Developers integrating AI assistants into existing web apps
integrate-fusion-agent FAQ
The connectToHostApp call will reject gracefully. Catch the rejection and set api to null. Hide or disable all agent UI when api is null — the agent features will be unavailable but the app continues to work.
Use newSession: true when starting a new task from a specific item (clears previous conversation). Omit it when continuing an existing conversation.
No. Implement only the capabilities you need: opening the panel, sending messages, and/or registering an agent server are independent.
Yes — the agent does NOT ask for confirmation before invoking actions. Be explicit in the action description that it is destructive and only call it when the user has explicitly approved the change.
Include relevant context the agent can act on immediately: item names, IDs, current state summary, and any filters or selections the user has made.
Full instructions (SKILL.md)
Source of truth, from cognitedata/builder-skills.
name: integrate-fusion-agent description: >- Integrates a Flows/Dune app with the Fusion built-in PAIA agent panel using @cognite/app-sdk. Use this skill whenever a developer wants to: open the agent panel from their app, send the agent a contextual message, let the agent read app state (resources), or let the agent call actions in the app. Triggers: "fusion agent", "PAIA", "agent panel", "sendAgentMessage", "sendAgentLayoutMode", "agent server", "registerAgentServer", "connectToHostApp", "agent integration", "agent sidebar", "app-sdk agent". Always use this skill instead of manually writing agent integration code — it sets up the correct lifecycle, graceful fallback, and recommended file structure. allowed-tools: Read, Glob, Grep, Edit, Write, Bash
Integrate Fusion Agent Panel
Wire a Flows/Dune app into the Fusion built-in PAIA agent using @cognite/app-sdk.
There are three independent capabilities — implement only the ones needed:
- Open the agent panel — a button that shows the sidebar/fullscreen agent UI
- Send the agent a message — inject context into the chat (e.g. on item click)
- Register an agent server — expose app state (resources) and actions the agent can call
Step 0 — Understand the app
Before writing any code, read:
package.json— detect package manager and whether@cognite/app-sdkis already installedsrc/App.tsx(or main entry) — understand current structure, existing SDK usage
Ask the user which of the three capabilities they need if it's not clear from context.
Step 1 — Install the SDK
If @cognite/app-sdk is not already in package.json, install it:
pnpm add @cognite/app-sdk # or npm/yarn depending on the app
Minimum required version: 0.3.1
Step 2 — Connect to the host app
All capabilities require a HostAppAPI instance. Obtain it once on mount and store it in React state or context. Always catch the rejection — the SDK throws when running outside Fusion (e.g. standalone vite dev).
Pattern for React apps:
// src/hooks/useHostApp.ts
import { useState, useEffect } from 'react';
import { connectToHostApp, type HostAppAPI } from '@cognite/app-sdk';
export function useHostApp(): HostAppAPI | null {
const [api, setApi] = useState<HostAppAPI | null>(null);
useEffect(() => {
connectToHostApp({ applicationName: 'my-app' })
.then(({ api: resolvedApi }) => {
// IMPORTANT: use the updater form here. Comlink proxies are callable
// objects, so setApi(proxy) causes React to invoke the proxy as a
// state-updater function — storing a Promise instead of the proxy.
// setApi(() => proxy) returns the proxy as the new state value.
setApi(() => resolvedApi);
})
.catch(() => {
// Running outside Fusion — agent features disabled, no-op
});
}, []);
return api;
}
Call useHostApp() at the root of your app and pass api down (or put it in context). When api is null, all agent UI triggers should be hidden or disabled — not shown as broken.
Step 3 — Opening the agent panel
Wire a persistent toolbar button (or equivalent trigger) to api.sendAgentLayoutMode.
import { type AgentLayoutPayload } from '@cognite/app-sdk';
// Open as sidebar (most common)
await api.sendAgentLayoutMode({ mode: 'sidebar' });
// Other modes
await api.sendAgentLayoutMode({ mode: 'fullscreen' });
await api.sendAgentLayoutMode({ mode: 'closed' });
The button should only render when api is not null — agent features are unavailable outside Fusion.
{api && (
<button onClick={() => api.sendAgentLayoutMode({ mode: 'sidebar' })}>
Open Assistant
</button>
)}
Step 4 — Sending the agent a message
Use sendAgentMessage on contextual triggers (e.g. "Analyse this item" button). Always pair it with sendAgentLayoutMode so the panel is visible.
// Open sidebar then inject context
await api.sendAgentLayoutMode({ mode: 'sidebar' });
await api.sendAgentMessage({
message: `Analyse the schedule for "${itemName}" and suggest how to reduce total duration.`,
newSession: true, // clears previous conversation — appropriate for contextual entry points
});
Use newSession: true when the user is starting a new task from a specific item. Omit it when you want to continue an existing conversation.
The message text should include relevant context the agent can act on immediately — item names, IDs, current state summary.
Step 5 — Registering an agent server
An agent server exposes resources (read-only app state the agent can read) and actions (tools the agent can invoke). Register once on mount, unregister on unmount.
Recommended file structure
Separate concerns so each piece is independently testable:
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
Resources
Resources are the agent's window into app state. Write description as you would a function docstring — the agent reads it to decide when to fetch the resource.
// src/features/agent/agentResources.ts
import { createAgentResource } from '@cognite/app-sdk';
import type { StorageService } from '../storage/StorageService';
export function buildAgentResources(storage: StorageService) {
return [
createAgentResource({
uri: 'my-app://current-state',
name: 'Current application state',
description:
'The current list of items visible in the app, their statuses, and any active filters. Read this before answering questions about what the user is looking at.',
async read() {
const data = storage.getAll();
return [{ type: 'json', data }];
},
}),
];
}
Each resource's read() returns an array of content parts:
{ type: 'json', data: unknown }— structured data (preferred; agent reasons over it directly){ type: 'text', text: string }— free-form text
Actions
Actions are tools the agent can invoke. Use snake_case names and Zod for parameter schemas. The .describe() on each field is the agent's documentation.
// src/features/agent/agentActions.ts
import { createAgentAction } from '@cognite/app-sdk';
import { z } from 'zod';
import type { DataService } from '../data/DataService';
export function buildAgentActions(dataService: DataService) {
return [
createAgentAction({
name: 'get_item_details',
description: 'Retrieve full details for a specific item by ID. Returns all fields including history.',
parameters: z.object({
item_id: z.string().describe('The ID of the item to retrieve'),
}),
async handler({ item_id }) {
const item = await dataService.getItem(item_id);
return { content: [{ type: 'json', data: item }] };
},
}),
];
}
Mutating actions: The agent does NOT ask the user for confirmation before calling actions — so use caution with actions that write data. Be explicit in the description that the action is destructive, and require the user to have approved before the agent calls it.
createAgentAction({
name: 'update_item_status',
description:
'Update the status of an item. Call this ONLY when the user has explicitly approved the change. The UI updates immediately.',
parameters: z.object({
item_id: z.string().describe('The item to update'),
status: z.enum(['active', 'closed', 'pending']).describe('The new status'),
}),
async handler({ item_id, status }) {
storage.updateStatus(item_id, status);
return { content: [{ type: 'json', data: { success: true } }] };
},
})
Lifecycle hook
// src/features/agent/useAgentServer.ts
import { useEffect } from 'react';
import { createAgentServer, registerAgentServer, type HostAppAPI } from '@cognite/app-sdk';
import { buildAgentActions } from './agentActions';
import { buildAgentResources } from './agentResources';
import { useStorageService } from '../storage/StorageServiceContext';
import { useDataService } from '../data/DataServiceContext';
export function useAgentServer(api: HostAppAPI | null): void {
const storage = useStorageService();
const dataService = useDataService();
useEffect(() => {
if (!api) return;
const server = createAgentServer({
uri: 'my-app', // namespaced by Fusion with instance ID — no need to be globally unique
actions: buildAgentActions(dataService),
resources: buildAgentResources(storage),
});
void registerAgentServer(api, server).catch((err: unknown) => {
console.warn('[agent] registerAgentServer failed:', err);
});
return () => {
void api.unregisterAgentServer('my-app').catch((err: unknown) => {
console.warn('[agent] unregisterAgentServer failed:', err);
});
};
}, [api, storage, dataService]);
}
Call useAgentServer(api) near the root of your component tree, after api is available.
Step 6 — Wire it all together
Call useHostApp() at the root, pass api to useAgentServer, and thread it down to any UI triggers:
// src/App.tsx
function App() {
const api = useHostApp();
useAgentServer(api); // registers resources + actions when api is ready
return (
<AppLayout>
<MainContent />
{api && (
<ToolbarButton onClick={() => api.sendAgentLayoutMode({ mode: 'sidebar' })}>
Open Assistant
</ToolbarButton>
)}
</AppLayout>
);
}
Dev vs. production
| Environment | connectToHostApp | Effect |
|---|---|---|
| Inside Fusion | Resolves with { api } | All features work |
Standalone vite dev | Rejects | Agent features silently disabled |
This is handled by the useHostApp hook above — no extra conditionals needed elsewhere.
Testing
Because buildAgentActions and buildAgentResources are pure factories that accept services as arguments, test them directly without mounting React:
// agentActions.test.ts
const mockDataService = { getItem: vi.fn().mockResolvedValue({ id: '1', name: 'Test' }) };
const [getItemAction] = buildAgentActions(mockDataService);
const result = await getItemAction.handler({ item_id: '1' });
expect(result.content[0].data).toEqual({ id: '1', name: 'Test' });
Known pitfalls
setApi(resolvedApi) stores a Promise, not the proxy
Comlink proxies are callable objects. React's useState setter, when given a function, calls it as fn(prevState) to compute the new state. Because a Comlink proxy responds to function calls (forwarding them to the remote), setApi(proxy) causes React to invoke the proxy, and the resulting Promise becomes the state value.
Symptom: api appears non-null (a Promise is truthy), but calling api.sendAgentLayoutMode(...) or checking typeof api.sendAgentLayoutMode returns nonsense.
Fix: Always use the updater form: setApi(() => resolvedApi).
typeof proxy.method === 'function' is always true
Comlink Proxy objects return 'function' for any property access via typeof. This means you cannot use typeof guards to detect whether a method is actually supported by the host. Use try/catch or .catch() on the call instead.
Checklist
-
@cognite/app-sdk@0.3.1+installed -
useHostApphook usessetApi(() => resolvedApi)— NOTsetApi(resolvedApi) -
useHostApphook catches rejection (outside Fusion), storesapiin state - Agent UI buttons only render when
apiis not null -
useAgentServerregistered on mount, unregistered on unmount -
registerAgentServerandunregisterAgentServercalls have.catch()handlers - Resource
descriptionfields explain what data is returned and when to read it - Action
namefields aresnake_case - Mutating actions warn in their
descriptionthat confirmation is required - Services injected into action/resource factories (not imported directly) — enables unit testing
Related skills
More from cognitedata/builder-skills and the wider catalog.

integrate-todo-list
Add structured task tracking to Flows apps with Atlas chat integration.

migrate-app-to-flows
Orchestrate full migration of legacy Dune apps to Flows app hosting infrastructure.

performance
Find and fix performance issues in Flows apps—re-renders, inefficient queries, pagination, and memory leaks.

pull-changes-resolve-conflicts
Safely integrate branch changes by analyzing conflicts before resolving, preserving intentional work.

reveal-3d
Embed interactive Cognite Reveal 3D CAD viewer in Flows apps with local bundled source.

security
Find and fix security issues in Flows apps before shipping—handles credentials, input validation, XSS, injection, and auth gaps.