# Agent activity

> The agent runs for the organization with their steps and the approvals that wait for a person.

- Block: [agents](https://bettersupabase.com/blocks/agents.md)
- Kind: component
- Data: loaded on the server through the block's client
- Page: https://bettersupabase.com/recipes/agent-run-trace

## Install the block

```bash
npx better-supabase add agents
```

## Web (Next.js)

Add the shadcn components:

```bash
npx shadcn add badge empty skeleton
```

From `apps/examples/nextjs/src/features/assistant/components/agent-activity.tsx` (https://github.com/ScaleDockHQ/better-supabase/blob/main/apps/examples/nextjs/src/features/assistant/components/agent-activity.tsx):

```tsx
import { ActivityIcon, InboxIcon } from "lucide-react";
import { getExtracted, getFormatter } from "next-intl/server";

import { Badge } from "@/components/ui/badge";
import {
  Empty,
  EmptyDescription,
  EmptyHeader,
  EmptyMedia,
  EmptyTitle,
} from "@/components/ui/empty";
import { Skeleton } from "@/components/ui/skeleton";
import { Link } from "@/i18n/navigation";

import { getActivity } from "../assistant-queries";
import { ApprovalButtons } from "./approval-buttons";

/** Render inside `<Suspense>`: the approval inbox, then the latest runs with their steps. */
export async function AgentActivity() {
  const [{ runs, approvals }, t, format] = await Promise.all([
    getActivity(),
    getExtracted("assistant"),
    getFormatter(),
  ]);

  const when = (iso: string) =>
    format.dateTime(new Date(iso), { dateStyle: "medium", timeStyle: "short" });

  return (
    <div className="flex flex-col gap-6">
      <section className="flex flex-col gap-3" aria-labelledby="approvals">
        <h2 id="approvals" className="text-sm font-medium">
          {t("Waiting for approval")}
        </h2>
        {approvals.length === 0 ? (
          <Empty className="border">
            <EmptyHeader>
              <EmptyMedia variant="icon">
                <InboxIcon />
              </EmptyMedia>
              <EmptyTitle>{t("Nothing to approve")}</EmptyTitle>
              <EmptyDescription>
                {t("Tool calls that need your approval show up here.")}
              </EmptyDescription>
            </EmptyHeader>
          </Empty>
        ) : (
          <ul className="divide-y rounded-xl border">
            {approvals.map((approval) => (
              <li
                key={approval.approvalId}
                className="flex flex-col gap-2 px-4 py-3 text-sm"
              >
                <p className="flex flex-wrap items-center gap-2">
                  <span className="font-medium">{approval.tool}</span>
                  <Link
                    href={`/assistant/${approval.chatId}`}
                    className="text-muted-foreground underline"
                  >
                    {approval.chatTitle === ""
                      ? t("Untitled chat")
                      : approval.chatTitle}
                  </Link>
                  <span className="text-xs text-muted-foreground tabular-nums">
                    {when(approval.createdAt)}
                  </span>
                </p>
                <pre className="overflow-x-auto rounded-md bg-muted p-2 text-xs whitespace-pre-wrap">
                  {approval.input}
                </pre>
                <ApprovalButtons
                  chatId={approval.chatId}
                  approvalId={approval.approvalId}
                />
              </li>
            ))}
          </ul>
        )}
      </section>
      <section className="flex flex-col gap-3" aria-labelledby="runs">
        <h2 id="runs" className="text-sm font-medium">
          {t("Latest runs")}
        </h2>
        {runs.length === 0 ? (
          <Empty className="border">
            <EmptyHeader>
              <EmptyMedia variant="icon">
                <ActivityIcon />
              </EmptyMedia>
              <EmptyTitle>{t("No runs yet")}</EmptyTitle>
              <EmptyDescription>
                {t("Every answer the assistant writes is a run.")}
              </EmptyDescription>
            </EmptyHeader>
          </Empty>
        ) : (
          <ul className="divide-y rounded-xl border">
            {runs.map((run) => (
              <li
                key={run.id}
                className="flex flex-col gap-2 px-4 py-3 text-sm"
              >
                <p className="flex flex-wrap items-center gap-2">
                  <Badge variant="outline">{run.status}</Badge>
                  <Badge variant="secondary">
                    {run.engine === "workflow" ? t("Durable") : t("Standard")}
                  </Badge>
                  <Link
                    href={`/assistant/${run.chatId}`}
                    className="flex-1 truncate underline"
                  >
                    {run.model ?? t("Default model")}
                  </Link>
                  <span className="text-xs text-muted-foreground tabular-nums">
                    {when(run.startedAt)}
                  </span>
                </p>
                {run.steps.length > 0 ? (
                  <ol className="flex flex-col gap-1 ps-4 text-xs text-muted-foreground">
                    {run.steps.map((step) => (
                      <li key={step.key} className="flex gap-2">
                        <span className="w-14 shrink-0">{step.status}</span>
                        <span className="truncate">{step.label}</span>
                      </li>
                    ))}
                  </ol>
                ) : null}
              </li>
            ))}
          </ul>
        )}
      </section>
    </div>
  );
}

export function AgentActivitySkeleton() {
  return <Skeleton className="h-96 w-full rounded-xl" />;
}
```

## Prompt

Paste this into a coding agent to build the recipe in your app.

### Web

```text
Build "Agent activity" in a React web app that uses shadcn/ui components. The agent runs for the organization with their steps and the approvals that wait for a person.

It uses the better-supabase `agents` block (Agents). It loads its data on the server through the block's client.

Steps:

1. If the app doesn't have the block yet, add the SQL with `npx better-supabase add agents` and apply the migration.
2. Add the shadcn components the recipe uses: `npx shadcn add badge empty skeleton`.
3. The recipe needs no glue: it calls the block's client on the server.
4. Write the component. Load the data on the server with the block's client, starting from the recipe's snippet (`apps/examples/nextjs/src/features/assistant/components/agent-activity.tsx`). Adapt it to the app's existing components, styling and data loading instead of copying it as is.
5. Check the result against the recipe and the block's docs: it typechecks, it reads the same block data, and it handles the loading and empty states the snippet handles.

Read the full code first:

- Recipe: https://bettersupabase.com/recipes/agent-run-trace.md
- Block docs: https://bettersupabase.com/docs/blocks/agents
```
