On this page

For AI agents: a documentation index is available at /docs/llms.txt. Append .md to any page URL for markdown, or send Accept: text/markdown.

Send agent events without the AI SDK

You can send Agent Analytics data without the AI SDK by emitting [Agent] events yourself, with track() from any standard Amplitude SDK or with a POST to the HTTP API. Amplitude builds sessions and runs enrichment on these events the same way it does for SDK-generated ones.

Use this path when the AI SDK doesn't fit your runtime. The AI SDK supports only Node.js and Python, so common cases are browsers and client-heavy SPAs, edge runtimes such as Cloudflare Workers and Deno, and languages such as Java, Go, and Ruby. It also covers AI app builders such as Lovable, Superblocks, v0, or Bolt, where the generated app is browser-first and importing @amplitude/ai fails on Node-only modules. If you can run the AI SDK, use it instead: it handles session lifecycle, redaction, and IDs for you.

Without the SDK, three jobs move to your code:

  • Session lifecycle: You emit [Agent] Session End.
  • Privacy: You redact content before you call track().
  • ID discipline: You generate and thread the session, turn, trace, and agent IDs.

Send the minimum events

Emit these events for every session:

  1. [Agent] User Message
  2. [Agent] Tool Call, one per invocation, only when your agent uses tools
  3. [Agent] AI Response
  4. [Agent] Session End

The wire format is a POST to Amplitude's HTTP endpoint with the [Agent] event contract as JSON:

bash
curl -X POST https://api2.amplitude.com/2/httpapi \
  -H "Content-Type: application/json" \
  -d '{
    "api_key": "YOUR_API_KEY",
    "events": [{
      "event_type": "[Agent] User Message",
      "user_id": "user-123",
      "event_properties": {
        "[Agent] Session ID": "ticket-42",
        "[Agent] Turn ID": 1,
        "[Agent] Trace ID": "trace-abc",
        "[Agent] Agent ID": "support-bot",
        "[Agent] Message ID": "msg-1",
        "$llm_message": { "text": "I need a refund" }
      }
    }]
  }'

For EU data residency, use https://api.eu.amplitude.com/2/httpapi. The rest of this page applies to both the standard SDK track() route and the raw HTTP route.

Every event also carries the shared properties ([Agent] Session ID, [Agent] Turn ID, [Agent] Trace ID, [Agent] Agent ID, [Agent] Env, and [Agent] Runtime) plus the properties its event type requires. Send the same user ID you use for product analytics, and set a unique insert_id on each event so retries don't create duplicates. For the required properties of each event, refer to Event contract in the taxonomy.

Always set [Agent] Agent ID. Without it, Amplitude accepts the event but can't group it into a session, so it stays out of Agent Analytics views. The HTTP API still returns 200, because it acknowledges receipt before Agent Analytics processes the event, so a 200 doesn't confirm correct grouping.

To send user ratings as [Agent] Score events, refer to Send feedback without the AI SDK.

Rules hand-rolled instrumentation gets wrong

  • $llm_message is an object: Send { text: "..." }. Amplitude ignores a plain string without an error, and the thread view shows no message content.
  • Turn ID identifies the exchange, not the event: Increment it once per user-message round trip, and stamp the same value on the user message, every tool call, and the AI response of that exchange. Ordering within a turn comes from event time, so emit tool calls before the AI Response in real execution order. Generate one [Agent] Trace ID per round trip and share it across the same events.
  • Stay inside the taxonomy: Don't invent event types or properties under the [Agent] prefix, because unregistered ones may not be queryable in charts. For business actions the agent performs, such as a purchase, a booking, or a recommendation click, emit your standard product events with your existing names and the same user_id. That keeps agent-driven and click-driven journeys comparable in one funnel.
  • Provide cost yourself: Amplitude doesn't calculate [Agent] Cost USD from model and token properties, so include it when you want cost reporting. Refer to How cost is calculated.
  • Token and cost properties live only on AI Response: Tool calls don't consume tokens. The server sums [Agent] Cost USD from every event it ingests, so a cost on a Tool Call or Session End inflates the session total.
  • Never reuse a session ID across users: A shared session ID merges different users' conversations and breaks per-user analytics.

Close sessions

Emit [Agent] Session End when the job ends, such as when a chat closes, a ticket resolves, or a run completes. The server closes the session on that event exactly as it does for an SDK-emitted close. Include an [Agent] Session ID that matches the session you're closing, and send the event to the project where you enabled Agent Analytics:

json
{
  "event_type": "[Agent] Session End",
  "user_id": "user-123",
  "event_properties": {
    "[Agent] Session ID": "session-abc",
    "[Agent] Agent ID": "support-bot"
  }
}

Amplitude stores any additional properties on the event, but they don't affect closing.

If you never emit Session End, the server closes the session after 30 minutes of inactivity. Let that timeout catch abandoned sessions. For how closing works and why late events miss enrichment, refer to How sessions close.

To keep sessions open longer than 30 minutes, set an idle timeout override in [Agent] Context. Don't build your own short idle timer instead, because rotating session IDs splits one conversation into several sessions. Refer to Configure session timeouts.

Redact content before tracking

Your redaction must run before track(). The most common mistake is gating message content and forgetting the other content-bearing properties. Apply your privacy mode to all four:

  • [ ] $llm_message.text on User Message and AI Response
  • [ ] [Agent] System Prompt on AI Response
  • [ ] [Agent] Tool Input and [Agent] Tool Output on Tool Call (redact recursively, because payloads are objects)
  • [ ] [Agent] Comment on Score

For metadata_only behavior, omit all four. You still get cost, latency, tokens, and session grouping. For full behavior, redact PII (emails, phone numbers, SSNs, credit card numbers, IP addresses) before tracking.

Browser and edge notes

  • Identity: A persistent anonymous UUID in localStorage, sent as user_id, gives anonymous visitors a stable identity. Standard identity resolution merges them when they sign in.
  • Session Replay: With the Session Replay plugin active, add the replay properties to every agent event so sessions link to recordings. Refer to Link to Session Replay.
  • Transport: Don't set the beacon transport globally. Calling setTransport('beacon'), for example inside a session-end handler, applies to the whole SDK permanently, so every later event sends once with no retry and can drop without an error. Use the default transport.
  • Latency and tokens: Measure these server-side, return them to the client with the response, and attach them to the events.

Instrument inside an AI app builder

In Lovable, Superblocks, or any prompt-driven builder, paste this prompt into the builder's agent and review what it produces:

text
Instrument this app's chat agent with Amplitude Agent Analytics using the standard
@amplitude/analytics-browser SDK (do NOT use @amplitude/ai, it is Node-only and will
crash this runtime). Emit these events via amplitude.track(): [Agent] User Message at
request start, one [Agent] Tool Call per tool invocation in execution order,
[Agent] AI Response after the reply, and [Agent] Session End when the chat genuinely ends
(no short idle timers; the server auto-closes idle sessions). On every event include
[Agent] Session ID (one stable UUID per conversation), [Agent] Turn ID (increment once
per user-message exchange and stamp the same value on all events of that exchange),
[Agent] Trace ID (new UUID per exchange), [Agent] Agent ID (a stable name for this agent),
and [Agent] Runtime: "browser". Message text goes in $llm_message as { text: "..." }
(an object, not a string) on User Message and AI Response only. Put [Agent] Model Name,
[Agent] Provider, token counts, [Agent] Latency Ms, and [Agent] System Prompt on AI
Response only. Put [Agent] Tool Name, [Agent] Tool Success, [Agent] Latency Ms,
[Agent] Invocation ID, and [Agent] Parent Message ID on Tool Call. Redact PII (emails,
phones, SSNs, cards, IPs) from all message text, system prompts, and tool payloads
before tracking. Use one persistent anonymous UUID from localStorage as the Amplitude
user_id. Never call setTransport("beacon"). Use functional state updates when computing
turn counters from UI state so follow-up messages aren't lost to stale closures.

Then send two messages, one that triggers a tool, and confirm in Live Events that:

  1. All events share one session ID.
  2. Each exchange shares one Turn ID and one Trace ID.
  3. Tool calls come before their AI Response.
  4. $llm_message.text shows in the session thread view.

Was this helpful?