# Let your agent call Server Actions

At the end of this tutorial, the agent marks an invoice as paid in the Next.js version of Nimbus CRM by running `setInvoiceStatus`, a Server Action that has no URL. The invoices table shows the new status without a reload.

In this tutorial, you:

- [Read the Data surface](#read-the-data-surface)
- [Generate the adapter](#generate-the-adapter)
- [Read the registry](#read-the-registry)
- [Publish a version](#publish-a-version)
- [Ask the agent](#ask-the-agent)
- [Check the registry in CI](#check-the-registry-in-ci)

## Before you begin

- The Next.js version of Nimbus CRM, or your own App Router app with Server Actions. To run the example:

  ```terminal
  git clone https://github.com/faustine-ai/example-nimbus-crm-next.git
  cd example-nimbus-crm-next
  npm install
  npm run dev
  ```

  The app runs at `http://localhost:3000`. Sign in with `admin@crm.test` and `admin123`. Its Server Actions are in `src/lib/actions/` and its server-only data functions are in `src/lib/dal/`. The only route handler, `src/app/api/events/route.ts`, streams the live activity feed.

- A Web Agent created for the app with Next.js as its technology, whose analysis has finished. See [Create a Web Agent](https://docs.faustine.ai/web-agents/create-a-web-agent/).
- The agent mounted in the app. See [Install in Next.js](https://docs.faustine.ai/web-agents/install-in-nextjs/). In the example, the component goes in `src/app/faustine-agent.tsx` and the root layout is `src/app/layout.tsx`. The tutorial does not need `identityJwt`.

## Read the Data surface

When the analysis of a Next.js app finishes, its summary shows a **Data surface** card. The card says which of the app's data the agent can reach.

1. On the analysis summary, find the **Data surface** card.
2. Select **Reachable with the adapter**. The group lists each Server Action and data function with its kind, its file and a description. In the example it has 36 entries, among them `setInvoiceStatus` from `src/lib/actions/invoices.ts` and `getInvoice` from `src/lib/dal/invoices.ts`.
3. Select **Reachable now**. In the example it has two entries, and both are the live feed: the route handler and the `EventSource` that reads it.

![The Data surface card of the example. Reachable now has 2 entries and Reachable with the adapter has 36, starting with the Server Actions of the app.](https://docs.faustine.ai/images/web-agents/data-surface-light.png)

**Not reachable** is empty in the example, because every page reads its data through a function in `src/lib/dal/`. A page that reads its data inline in the component would appear there, with a **Suggested** line that names the data function to extract.

Without the adapter, the agent can open pages of this app and cannot read or change an invoice.

## Generate the adapter

`faustine-next` is the command-line tool of `@faustine-ai/next`. It writes the adapter's files from the operations in the **Data surface**.

1. In the portal, go to [**Web Agents**](https://auth.faustine.ai/go?to=%2Fweb-assistants), select your agent, and find the **Install the assistant** card in the **Overview** tab.
2. Select **By hand**.
3. Select **Create install command**. The **Adapter** step now contains a setup code that starts with `fns_`. The code can be used once and expires after 30 minutes.
4. In the app's directory, print the plan. `<SETUP_CODE>` is the code from the card.

   ```terminal
   npx faustine-next init --setup <SETUP_CODE> --dry-run
   ```

   ```text
   Plan for Web Agent <AGENT_ID> — registry 55b2eb23eb603786

     create    src/faustine/registry.ts
     create    src/app/api/faustine/[...op]/route.ts
     create    AGENTS.md

     36 operation(s) bound:
       - contacts.saveContact
       - contacts.deleteContact
       - companies.saveCompany
       ...

   Nothing written (--dry-run).
   Adapter token stored at ~/.config/faustine/adapters/<AGENT_ID>.json.
   ```

   This run exchanges the setup code for an adapter token and stores the token on your machine.

5. Write the files. The same code works again on this machine because the token is already stored.

   ```terminal
   npx faustine-next init --setup <SETUP_CODE>
   ```

   Outside a terminal, such as in a script or a coding agent, add `--yes`.

`init` also reports the registry to Faustine. Reload the **Overview**: the card is now titled **Faustine in this app** and shows the state of the adapter.

![The Faustine in this app card: the registry hash the live build serves, 36 operations behind Server Actions or server-only functions, and the steps Generated, In the repo and Deployed.](https://docs.faustine.ai/images/web-agents/adapter-card-light.png)

## Read the registry

The registry is the list of functions the agent may call. Nothing outside it can run through the adapter.

1. Open `src/faustine/registry.ts`.

   ```ts filename="src/faustine/registry.ts"
   import { createRegistry } from '@faustine-ai/next'

   export default createRegistry({ webAgent: '<AGENT_ID>', formatVersion: 1 }, {
     // One invoice with its line items and totals
     'invoices.getInvoice': { fn: () => import('@/lib/dal/invoices').then((m) => m.getInvoice), params: ['id'] },
     // Changes an invoice's status (draft, sent, paid, overdue, void)
     'invoices.setInvoiceStatus': { fn: () => import('@/lib/actions/invoices').then((m) => m.setInvoiceStatus), params: ['invoiceId', 'status'] },
     // Deletes a pipeline
     'pipelines.deletePipeline': { fn: () => import('@/lib/actions/pipelines').then((m) => m.deletePipeline), params: ['pipelineId'] },
     // More lines omitted.
   })
   ```

   Each line has the name the agent knows, an import of your function and the function's parameter list.

2. Delete the line of `pipelines.deletePipeline`, with the comment above it. Deleting a pipeline removes its stages and deals, and in this tutorial the agent should not be able to do that. A removed line keeps that function private, and `sync` does not add it back.
3. Open `src/app/api/faustine/[...op]/route.ts`. It mounts the handler that runs the registry's functions.

   ```ts filename="src/app/api/faustine/[...op]/route.ts"
   import { createFaustineHandler } from '@faustine-ai/next'
   import registry from '@/faustine/registry'

   export const { GET, POST } = createFaustineHandler(registry)
   ```

4. Ask the handler what it serves.

   ```terminal
   curl http://localhost:3000/api/faustine/manifest
   ```

   ```json
   {
     "webAgent": "<AGENT_ID>",
     "formatVersion": 1,
     "registryHash": "7746fa232c5a76e3",
     "buildSha": null,
     "operations": [
       { "op": "activities.listActivities", "schema": false, "confirm": false },
       { "op": "auth.getCurrentUser", "schema": false, "confirm": false }
     ],
     "enabled": [],
     "served": "none"
   }
   ```

   The example lists 35 operations. Two are shown here.

`operations` is what this build binds. `enabled` is what the served version of the agent turns on. While it is empty, the handler refuses every call.

## Publish a version

The registry says what the app is willing to run. The version of the agent that an environment serves decides what the agent may call. A new agent has a draft and no version yet.

1. In the portal, open the agent's **Actions** tab and check that the **Enabled** switch of `setInvoiceStatus` is on.
2. Select **Publish**. See [Publish a version](https://docs.faustine.ai/web-agents/publish-a-version/).
3. Request the manifest again.

   ```terminal
   curl http://localhost:3000/api/faustine/manifest
   ```

The handler keeps the list of enabled operations for 60 seconds, so the change can take a minute to appear. `enabled` now contains `invoices.setInvoiceStatus` and `served` is `ok`. Your app was not rebuilt or deployed for this.

## Ask the agent

1. Open `http://localhost:3000/invoices`. The table lists the invoice **INV-0002** with the status **Sent**.
2. Open the widget and send this message.

   ```text
   Mark invoice INV-0002 as paid.
   ```

> **Checkpoint:** The agent answers that the invoice is marked as paid, and the **INV-0002** row shows the status **Paid** without a reload of the page.

![Nimbus CRM on the Invoices page with the widget open. The agent answers that INV-0002 is now marked paid and shows the invoice's card.](https://docs.faustine.ai/images/web-agents/tutorial-invoice-paid.png)

The agent sent `POST /api/faustine/invoices.setInvoiceStatus` to your app with your cookies. The handler ran `setInvoiceStatus` in your session, the function revalidated the invoices pages, and the agent asked the Next.js router to refresh the page.

## Check the registry in CI

A Server Action added later is not in the registry until someone runs `sync`. `sync --check` fails the build when the registry is out of step.

1. Commit `src/faustine/registry.ts`, the route handler and `AGENTS.md`.
2. Store the adapter token file of your machine, `~/.config/faustine/adapters/<AGENT_ID>.json`, as a CI secret named `FAUSTINE_ADAPTER_TOKEN_FILE`.
3. Add the check to your workflow.

   ```yaml filename=".github/workflows/ci.yml"
   - name: Check the Faustine adapter
     env:
       FAUSTINE_CONFIG_DIR: ${{ runner.temp }}/faustine
       FAUSTINE_ADAPTER_TOKEN_FILE: ${{ secrets.FAUSTINE_ADAPTER_TOKEN_FILE }}
     run: |
       mkdir -p "$FAUSTINE_CONFIG_DIR/adapters"
       printf '%s' "$FAUSTINE_ADAPTER_TOKEN_FILE" > "$FAUSTINE_CONFIG_DIR/adapters/<AGENT_ID>.json"
       npx faustine-next sync --check
   ```

The step exits with `0` while the registry matches and with `1` when `sync` would change it. The line you removed does not count as a change: the check reports it as left out. To fix a failing check, see [Keep the adapter in sync](https://docs.faustine.ai/web-agents/sync-the-adapter/).

## What you built

The app has a registry of the server functions its agent may call and a route handler that runs them in the user's session. The agent's **Settings** tab shows the state of the adapter and its tokens under **Faustine adapter**.

## Next steps

- [Keep the adapter in sync](https://docs.faustine.ai/web-agents/sync-the-adapter/): update the registry when the app's Server Actions change.
- [Require confirmation for an action](https://docs.faustine.ai/web-agents/require-confirmation/): have the agent ask before it changes or deletes an invoice.
- [Verify your users with a signed token](https://docs.faustine.ai/web-agents/verify-users/): sign the user's identity in the root layout.
- [The Faustine adapter for Next.js](https://docs.faustine.ai/web-agents/nextjs-adapter/): how the handler decides what to run.
