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

# Instrument with an AI agent

> Instructions for a coding agent to add Foglamp tracing to an app.

<Note>
  This page is written for an **AI coding agent** (Claude Code, Cursor, …) asked
  to add Foglamp tracing to a codebase. If you're a human, the
  [Quickstart](/quickstart) is friendlier.
</Note>

You are instrumenting a **Vercel AI SDK** app so its runs appear in Foglamp.
Foglamp captures model/tool/step boundaries from the AI SDK and renders them as
traces (with cost, tokens, latency). Make the **smallest safe change** that gets
one real run flowing, then enrich.

## Rules

* **Check the AI SDK version first and pick the matching path** (step 2):
  `wrap()` from `foglamp/wrap` on **AI SDK v4–v6**, or `fog.integration(...)` on
  **v7**. Both capture identical traces — never upgrade the app's AI SDK just to
  instrument it. Steps 3–5 below (mapping, flush, verify) apply to both paths.
* **Never refactor working AI code to instrument it.** `wrap()` covers
  `generateText`/`streamText`/`generateObject`/`streamObject` **and the
  `ToolLoopAgent` / `Experimental_Agent` classes** — wrap in place; do not
  rewrite agent classes into `generateText` calls or restructure pipelines.
* Prefer the installed package's types/README over memory. Don't invent SDK APIs
  or hand-wire ingest endpoints — only use `foglamp`'s public API below.
* The SDK is a **silent no-op without `FOGLAMP_API_KEY`** — safe to add in every
  environment. Nothing throws and no spans are sent until the key is set.
* **Names are static string literals.** `agentName`, `workflowName`, and
  `traceName` must be written as literal strings in the source — never template
  literals, concatenation, or variables. Anything dynamic (a slug, URL, id,
  date) goes in `metadata`, `workflowRunId`, or `sessionId` instead. See
  [the mapping rules](#3-map-the-codebase-to-foglamps-model).
* Instrument **one real entry point first**, verify a trace appears, then expand.

## 1. Install & configure

Install `foglamp` with the repo's own package manager (check the lockfile —
don't introduce a second one):

```bash theme={null}
npm i foglamp        # or: pnpm add / yarn add / bun add
```

```bash .env theme={null}
FOGLAMP_API_KEY=fl_your_key_here
# Hosted ingest is the default. Only set this when self-hosting:
# FOGLAMP_INGEST_URL=http://localhost:4000/ingest
```

## 2. Wire up Foglamp — pick the path for the installed version

**Check the installed `ai` version first** (read the lockfile / `package.json`),
then follow the matching path. Both capture identical traces; only the wiring
differs, and steps 3–5 apply to both. Every traced call needs a `traceName`
**or** an `agentName`.

### AI SDK v4, v5, or v6 — `wrap()`

Wrap the `ai` module once, then bind a context with `fog.with(...)`; the
returned functions keep the AI SDK's own, fully-typed signatures. `wrap()` also
covers `generateObject`/`streamObject` and the `ToolLoopAgent` /
`Experimental_Agent` classes — instrument them in place; do not rewrite agent
code into `generateText` calls.

```ts theme={null}
import * as ai from "ai";
import { wrap } from "foglamp/wrap";

const fog = wrap(ai);
const { generateText } = fog.with({ agentName: "summarizer" });

await generateText({ model, prompt }); // traced automatically
```

See [AI SDK v4–v6 (wrap)](/sdk/wrap) for `fog.run(...)` (ambient context) and
the per-call `foglamp: {...}` option.

### AI SDK v7 — `fog.integration()`

Attach the integration to each `generateText` / `streamText` call via the
`telemetry` option.

```ts theme={null}
import { foglamp } from "foglamp";
import { generateText } from "ai";

const fog = foglamp();

await generateText({
  model,
  prompt,
  telemetry: {
    integrations: [fog.integration({ agentName: "summarizer" })],
  },
});
```

## 3. Map the codebase to Foglamp's model

This is the step that makes the dashboard useful, so spend real effort here:
read the codebase and decide what its agents, workflows, and sessions actually
are before writing any context. The full context surface:

| Property                         | What it is                                                                           | Good values                                                                                                                                                                                                                                                      |
| -------------------------------- | ------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `agentName`                      | The named, reusable LLM behavior responsible for the call                            | Stable, low-cardinality identifiers from the code's own vocabulary: `"support-triage"`, `"summarizer"`. Never per-request values.                                                                                                                                |
| `workflowName` + `workflowRunId` | A named multi-step process, and one execution of it                                  | `workflowName` stable like an agent name (`"ticket-pipeline"`); `workflowRunId` an id the app already has for this execution — request id, job id, ticket id.                                                                                                    |
| `traceName`                      | Human label for a one-off call that isn't an agent                                   | The call's purpose: `"classify-email"`.                                                                                                                                                                                                                          |
| `sessionId`                      | Ties traces in one conversation or user thread together, across workflows and agents | The app's existing chat/conversation/thread id. Conversations only — a batch/cron/pipeline run id is a `workflowRunId`, not a session.                                                                                                                           |
| `customer` *(optional)*          | The end-customer this call serves, for per-customer cost. `{ id, name?, imageUrl? }` | Only set it if the app serves distinct end-customers/tenants. `id` is a **dynamic, high-cardinality** identifier (the customer's id) — the one place an id belongs in a first-class field rather than `metadata`. `name`/`imageUrl` are optional display fields. |
| `metadata`                       | Everything else, as string key/values                                                | `userId`, tenant, environment, prompt version, A/B variant.                                                                                                                                                                                                      |

Rules the SDK enforces (at the type level and at ingest):

* Every call needs **`traceName` or `agentName`** (both is fine — the trace is
  attributed to the agent and displays the `traceName`).
* **`workflowName` and `workflowRunId` go together** — one without the other is
  an error. Calls sharing a `workflowRunId` are stitched into one run on the
  Workflows timeline, so the id must be shared by every call in the run but
  unique per execution.

How to decide, concretely:

* **Find the agents.** A class, module, or function that owns a system prompt
  and is invoked from more than one place is an agent → `agentName`. Name it
  after what the code calls it.
* **Find the pipelines.** A request handler or job that makes several model
  calls (or calls several agents) before producing its result is a workflow →
  same `workflowName` + `workflowRunId` on *every* call in it, including calls
  made by nested agents. **Use `fog.run(context, fn)` at the handler/job entry
  point** — it sets the context ambiently for everything inside (however
  deeply nested), so you don't thread a trace parameter through every function
  signature between the handler and the AI calls:

  ```ts theme={null}
  await fog.run(
    { workflowName: "ticket-pipeline", workflowRunId: ticket.id, metadata: { userId } },
    () => handleTicket(ticket) // every instrumented call inside is attributed
  );
  ```

  On **AI SDK v7**, calls a tool makes back into the model inherit the parent
  call's workflow/session context automatically (see [SDK
  overview](/sdk/overview#nested-calls-inside-tools)) — so `fog.run()` is mainly
  for the *entry point* and for the v4–v6 [`wrap`](/sdk/wrap) path.
* **Find the threads.** If the app has conversations (a chat, a support thread),
  pass its id as `sessionId` on every call serving that thread. A session is a
  **conversation** — a thread where a user goes back and forth. If no human is
  conversing, there is no session: a batch run, cron job, pipeline execution,
  billing period, or engagement cycle is **not** a session, even though its id
  would technically group traces. Group executions with
  `workflowName`+`workflowRunId` and put longer-lived business ids
  (campaign, cycle, tenant) in `metadata`. When in doubt, omit `sessionId` —
  it is optional.
* **Don't overload the names.** High-cardinality values (user ids, slugs,
  URLs, dates, ticket numbers) belong in `workflowRunId`, `sessionId`, or
  `metadata` — never in `agentName`/`workflowName`/`traceName`, which should
  each have a small, stable set of values. The mechanical check: every name
  must be a **string literal** at the call site. If you catch yourself writing
  a template literal or passing a variable, the dynamic part is metadata:

  ```ts theme={null}
  // ❌ Wrong — every page produces a distinct "agent", ruining grouping
  fog.integration({ agentName: `writeQa(${site}/${slug})` });

  // ✅ Right — one agent, the page identified in metadata
  fog.integration({ agentName: "writeQa", metadata: { site, page: slug } });
  ```

```ts theme={null}
fog.integration({
  agentName: "retriever",                 // who: the reusable behavior
  workflowName: "support-ticket",         // what process this run is part of
  workflowRunId: ticket.id,               // which execution (shared, pre-existing id)
  sessionId: conversation.id,             // which user thread
  metadata: { userId: user.id, env: process.env.NODE_ENV ?? "dev" },
});
```

The context fields are identical on both paths. On **v4–v6** the same object
goes into `fog.with({...})`, a per-call `foglamp: {...}` key, or
`fog.run({...}, fn)` for run-scoped context — see [wrap](/sdk/wrap).

## 4. Flush in serverless

Long-running servers (Node, Bun) flush on an interval automatically. Serverless
is **auto-detected** (`VERCEL` / `AWS_LAMBDA_FUNCTION_NAME` env vars) and
switches to per-call flushing — so check what the deployment target needs
before adding flush plumbing:

* **Vercel**: nothing to do — foglamp reads `waitUntil` from the runtime's
  request context automatically (no `@vercel/functions` dependency needed) to
  keep the invocation alive while the batch sends.
* **Cloudflare Workers / other serverless**: pass `waitUntil` in the config
  (`foglamp({ waitUntil: ctx.waitUntil })`), or `await fog.flush()` before each
  handler returns.

## 5. Optional — live HUD (dev only)

If the app has a **React UI** and a local dev server, offer to wire up the live
HUD: a dev-only floating overlay that streams runs (steps, tool calls, tokens,
cost) on top of the app as the user develops — a great first-run "wow". It needs
**no API key** and is a no-op in production / on edge / serverless, so it's safe
to leave in. Two lines:

* **Server**: pass `hud: true` to the existing `foglamp({ ... })` call.
* **Client**: render `<FoglampHUD />` from `foglamp/hud` once near the root of
  the client app (e.g. the root layout).

```tsx theme={null}
import { FoglampHUD } from "foglamp/hud";
// …near the app root, e.g. in the root layout:
<FoglampHUD />
```

The route/handler that constructs `foglamp({ hud: true })` must run on the
**Node runtime** (not edge). It needs nothing beyond `foglamp` + React. Skip
this step entirely if there's no React frontend. Full reference:
[Live HUD](/sdk/hud).

## 6. Hand off

A trace only exists once a real model call runs — and that's the user's job,
not yours. **Do not write smoke tests, test scripts, demo endpoints, or
synthetic "first trace" calls** — they add code the user has to delete and
burn real model tokens. Once the build/typecheck passes, finish by telling the
user **exactly how to trigger their own instrumented flows** — which command to
run, which page to hit, which job to kick off — so they can run the app and
watch the first traces appear in the dashboard (Overview / Traces) — and stream
live in the HUD, if they enabled it.

See the [SDK reference](/sdk/overview) and [data model](/concepts/data-model)
for every option.
