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.

Render agent artifacts

An artifact is a small, versioned JSON envelope your agent attaches to a tool call to describe a visual it showed a user. Agent Analytics validates the envelope and redraws that visual as a card in the session view, so a reviewer sees what the user saw instead of the JSON behind it. Amplitude draws the card with built-in renderers that follow the viewer's theme, so you never ship UI code and Amplitude never executes yours.

Emit artifacts when the visual was the answer and the raw payload doesn't tell you whether the answer was any good, such as a chart your agent generated or a set of choices it offered. If you only need the underlying numbers for analysis, you don't need artifacts: tool content already lands as event properties you can query in charts and cohorts.

Agent Analytics renders valid $agent_artifact envelopes automatically for every organization.

How it works

  1. Your agent produces a visual for its user, as it does today.
  2. Alongside it, your code attaches an artifact envelope to the tool call's input or output.
  3. Agent Analytics recognizes the envelope, validates it, and renders a card in both the conversation view and the trace panel.
  4. The raw tool payload stays visible next to the card. Amplitude never replaces or hides it.

If a payload doesn't validate, Agent Analytics shows a short reason next to the raw output instead of a card. It never guesses at your data or renders a partial result.

Instrument artifacts with your coding agent

Use your coding agent to add artifact serializers where your application produces visuals. Open the repository in your coding agent, then copy and run this prompt. If your application doesn't send sessions to Agent Analytics yet, set up instrumentation first.

text
Add artifact instrumentation to this application's existing Amplitude
Agent Analytics integration.

Read the artifact contract:
https://amplitude.com/docs/amplitude-ai/agent-analytics/render-artifacts
Also read the instrumentation guide for the SDK or ingest path this
repository uses. If you can't access the documentation, ask me for it
before implementing the contract.

1. Find the code that produces charts, summaries, tables, choices, or
   static HTML shown to users. Trace each visual to its tool call and
   existing instrumentation. Don't infer a displayed visual from arbitrary
   JSON. Find the actual data used to render each visual; a chart ID, URL,
   or configuration without values isn't a data snapshot. Preserve the
   displayed filters, time range, timezone, and transformations. If that
   data or an editable emission point is unavailable, report the gap.

2. Write reusable serializer functions in the project's language that
   return a root-level $agent_artifact envelope with v: 1, the appropriate
   type, and its required fields. Prefer chart, summary, table, or options.
   If those types would lose meaningful content, implement a static html
   fallback when feasible. Examples include choices with descriptions,
   tables with semantic highlights, and reports containing several visuals.
   Generate self-contained HTML/CSS/inline SVG from the actual source data,
   escaping dynamic text. Follow the documented HTML restrictions and
   per-type size limits. The result captures what was shown; controls stay
   inert. For script-dependent visuals, generate a faithful static snapshot
   from their data rather than simply removing scripts. Before skipping a
   visual, evaluate both structured types and static html. If neither fits,
   explain what content or meaning would be lost or what data is missing.

3. Derive labels, values, units, ordering, and scope from the application
   code. Ask me about ambiguous mappings, such as cents versus dollars or
   missing values. Don't invent values, silently truncate data, or change
   the meaning of the visual to make an envelope validate.

4. Attach the envelope to the corresponding tool output, or tool input
   for an input-side artifact such as a choice prompt. Follow the existing
   SDK, HTTP, or OTLP path and its serialization rules. Reuse session,
   trace, and tool identifiers. Preserve application behavior and existing
   payload fields, and avoid recording the same tool call twice. Account
   for every visual a tool produces. For multiple visuals, evaluate one
   static html report that preserves all meaningful content within the
   documented limits. If that isn't feasible and no structured type fits,
   explain the limitation and ask me how to proceed. Don't silently select
   one visual or invent a multi-artifact envelope.

5. Preserve content-capture and redaction settings. Don't enable content
   capture to bypass metadata-only mode. Redact sensitive values before
   serializing the envelope, and use synthetic fixtures for tests.

6. Add focused tests for representative inputs, optional or missing
   fields, and invalid or oversized data. Validate the serialized output
   against the artifact contract. Show the changed files, mapping
   assumptions, test results, and a representative emitted envelope.
   Explain how to verify the artifact in an Agent Analytics test session.
   Distinguish local serializer/contract checks from live ingestion and
   visual verification; report which checks you actually performed.
   For HTML fallbacks, verify that meaningful content remains visible under
   the documented rendering restrictions.

7. Finish with an instrumented / skipped / needs a decision checklist.
   For each visual path, name its source and emission point, coverage,
   representation, and any limitation or unanswered mapping question. For
   skipped visuals, explain why neither a structured type nor static html
   works. Don't claim full coverage based on one successful example.

Review the generated changes and coverage checklist, especially skipped visuals, units, and field mappings, before using them. After running the tests, send a test session and confirm the artifact matches the visual your agent showed. If Agent Analytics reports Artifact not rendered, use the validation reason to correct the serializer.

To implement the serializer yourself, use the envelope, artifact types, and tool-call examples.

The envelope

Every artifact is a JSON object under a single reserved key. The key is deliberately vendor-neutral and nothing in the payload grammar is Amplitude-specific, so the same builder can emit to another platform that adopts this shape — you change the key, not your code.

json
{
  "$agent_artifact": {
    "v": 1,
    "spec": "https://amplitude.com/docs/amplitude-ai/agent-analytics/render-artifacts",
    "type": "chart",
    "title": "Monthly payouts",
    "caption": "August was the highest month.",
    "scope": { "label": "Aug 2026" },
    "locale": "en-IN",
    "payload": {}
  }
}

Two conventions apply everywhere:

  • unit is a display string, not a concept. Send "INR", "%", "ms", "req/s", or anything else. Amplitude appends it to values as-is and never interprets it. Number formatting comes from locale.
  • Colors are semantic, never literal. Send primary, success, or danger and Amplitude maps them to theme colors that work in light and dark mode, and that stay distinguishable for color-blind readers. Hex values, fonts, and sizes aren't part of the contract.

Attach the envelope to a tool call

Artifacts travel on the tool call that produced them. Pass the envelope as the tool's output, or as input when the artifact is the input, as with a question you asked the user.

const artifact = {
  $agent_artifact: {
    v: 1,
    type: "chart",
    title: "Monthly payouts",
    payload: {
      shape: "categorical",
      kind: "bar",
      unit: "INR",
      items: [
        { label: "Aug 2026", value: 37279.7, color: "danger" },
        { label: "Jul 2026", value: 23925, color: "primary" },
      ],
    },
  },
};

session.trackToolCall("render_payouts_chart", latencyMs, true, {
  input: JSON.stringify({ range: "last-3-months" }),
  output: JSON.stringify(artifact),
});

Tool content comes from the input and output options. Amplitude ignores other spellings without an error, so your artifact never arrives. That includes toolInput and toolOutput, which are the names of the resulting event properties. If you rely on patch() for zero-code instrumentation, note that auto-extracted tool calls carry no input or output, so artifacts need an explicit trackToolCall. Refer to Track tool calls in the SDK reference.

Emit from any ingest path

The envelope is the same on every path. What changes is only where you put it: artifacts ride the tool call's existing content fields, so any path that carries tool content carries artifacts.

AI SDK

Pass the envelope as the output (or input) option, as in the examples above. Amplitude serializes it into the [Agent] Tool Output property.

HTTP API or a standard Amplitude SDK

Set the [Agent] Tool Output property directly on an [Agent] Tool Call event. Refer to Set up Agent Analytics for the full event contract and the ID fields every event needs.

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] Tool Call",
      "user_id": "user-123",
      "event_properties": {
        "[Agent] Session ID": "ticket-42",
        "[Agent] Turn ID": 2,
        "[Agent] Trace ID": "trace-abc",
        "[Agent] Agent ID": "support-bot",
        "[Agent] Tool Name": "render_refund_summary",
        "[Agent] Tool Success": true,
        "[Agent] Tool Input": "{\"order_id\":\"A-1\"}",
        "[Agent] Tool Output": "{\"$agent_artifact\":{\"v\":1,\"type\":\"summary\",\"title\":\"Refund total\",\"payload\":{\"headline\":{\"value\":42.5,\"label\":\"Refunded\",\"unit\":\"USD\"}}}}"
      }
    }]
  }'

Both properties take a JSON string, so serialize the envelope before assigning it.

OTLP

Put the envelope in whichever result attribute your instrumentation already uses on the tool span. Amplitude reads the first one present, in this order:

Tool input follows the same pattern, from gen_ai.tool.call.arguments or input.value. The tool name comes from gen_ai.tool.name or tool.name. Refer to Set up Agent Analytics for the OTLP endpoint and headers.

Artifact types

chart

Describe a chart by its data shape, which Amplitude validates, plus an optional kind that hints at the picture you drew.

kind is a free string: bar, column, pie, donut, line, area, or anything else you call your chart. Amplitude draws a kind that has its own renderer that way. Anything else renders as the shape's default presentation with a visible note saying so, so a reviewer always knows they're looking at a substitute. Shapes that have no dedicated renderer yet (steps, points, matrix) render as a labeled table and carry the same note.

Optional chart fields: unit, xLabel, yLabel, and highlightedIndex (for categorical, to emphasize one item).

json
{
  "$agent_artifact": {
    "v": 1,
    "type": "chart",
    "title": "Monthly cash flow",
    "locale": "en-IN",
    "payload": {
      "shape": "series",
      "kind": "line",
      "unit": "INR",
      "xLabel": "Month",
      "yLabel": "Amount",
      "series": [
        {
          "label": "Cash-in",
          "color": "success",
          "points": [
            { "x": "Apr 2026", "y": 222389 },
            { "x": "May 2026", "y": 42810 }
          ]
        },
        {
          "label": "Cash-out",
          "color": "danger",
          "points": [
            { "x": "Apr 2026", "y": 61453 },
            { "x": "May 2026", "y": 32580 }
          ]
        }
      ]
    }
  }
}

summary

One number card at whatever density you need: a bare headline, a headline with a comparison, or a full overview with supporting metrics and facts. Send a headline; every other field is optional.

json
{
  "$agent_artifact": {
    "v": 1,
    "type": "summary",
    "title": "Net cash flow",
    "scope": { "label": "Sep 2026" },
    "payload": {
      "headline": {
        "value": 25008,
        "label": "Net cash flow",
        "unit": "SAR",
        "tone": "positive"
      },
      "delta": { "value": 80192, "direction": "up", "comparedTo": "vs August" },
      "metrics": [{ "label": "Entries", "value": 41, "tone": "neutral" }],
      "facts": [
        { "label": "All-time balance", "value": 918220, "tone": "positive" }
      ],
      "suggestions": ["Show entries for this month"]
    }
  }
}

tone is positive, negative, or neutral. It carries direction, not judgment. suggestions are the follow-ups your agent offered; they render as labels, because the session view is a record of what happened, not a place to continue the conversation.

options

A choice you presented to the user. Because the user has already answered by the time anyone reviews the session, the choices render as labels.

json
{
  "$agent_artifact": {
    "v": 1,
    "type": "options",
    "payload": {
      "question": "Which quarter should I show?",
      "options": [
        { "id": "2026-q1", "label": "Jan – Mar 2026" },
        { "id": "2026-q2", "label": "Apr – Jun 2026" }
      ],
      "reason": "too_many_results"
    }
  }
}

Attach this one to the tool's input, since the question is what your tool received. reason is your own short code for why the agent asked; it's stored for analysis and not rendered.

table

json
{
  "$agent_artifact": {
    "v": 1,
    "type": "table",
    "title": "Spend by region",
    "payload": {
      "columns": ["Region", "Sessions", "Spend"],
      "rows": [
        ["EU", 1200, 4517.5],
        ["US", 880, 91000]
      ]
    }
  }
}

Cells are strings or numbers; numbers format by locale and align right. Every row needs exactly one cell per column. Amplitude rejects a row of any other length instead of padding it, because padding would misstate your data.

html

For a visual the other types genuinely can't express, send self-contained markup.

json
{
  "$agent_artifact": {
    "v": 1,
    "type": "html",
    "title": "Custom SLA gauge",
    "payload": {
      "html": "<html><head><style>…</style></head><body>…</body></html>"
    }
  }
}

Amplitude enforces all of these:

  • Static markup only: Amplitude rejects script tags, and the rendering frame can't execute scripts at all. Interactivity belongs in the declarative types.
  • Fully self-contained: inline your CSS and data. Amplitude blocks images (including data: images) and external requests for fonts, stylesheets, and APIs. Use HTML, CSS, or inline SVG for visuals.
  • No navigation: Amplitude removes link destinations, so links display as content without navigating when clicked.
  • Bring your own styling: the frame isolates your document, so Amplitude's theme doesn't reach inside it.
  • 100,000 characters maximum.

Amplitude shows an html artifact collapsed, with a button to render it, then draws it in a sealed frame that can't reach the surrounding page.

Prefer a declarative type wherever one fits: those cards follow the viewer's theme, work with screen readers, and appear in exports. html trades all of that for freedom of layout.

Limits

Amplitude rejects oversized payloads instead of truncating them, because a shortened chart would show the wrong picture.

Privacy

Artifact payloads travel on your tool content, so they follow the same content settings as the rest of your instrumentation, including your redaction configuration. Two things to know:

  • Redact before you serialize: redaction that rewrites text can corrupt JSON mid-payload, which makes the artifact unparseable and the card disappear. Remove or mask values as you build the envelope.
  • Markup carries data in more places than text: for html, check attributes and inline styles, not only visible strings.

If your organization uses metadata_only content mode, Amplitude doesn't capture tool content, so artifacts don't render.

Troubleshooting

Was this helpful?