npx skills add ...
npx skills add datadog-labs/agent-skills --skill llm-obs-experiment-analyzer
npx skills add datadog-labs/agent-skills --skill llm-obs-experiment-analyzer
Analyze LLM experiment results. Handles single or comparative experiments, exploratory or Q&A modes. Use when user says "analyze experiment", "compare experiments", "analyze against baseline", or provides one or two experiment IDs for analysis.
Detection — At the start of every invocation, before taking any action, determine which backend to use:
--backend pup anywhere in their invocation → use pup mode immediately, regardless of whether MCP tools are present. Skip steps 2–4.get_llmobs_experiment_summary (with or without the mcp__datadog-llmo-mcp__ prefix) appears in your available tools.get_llmobs_experiment_summary appears in your active tool list and use that exact name (prefixed or unprefixed) for every MCP tool call this invocation. All other experiment tools follow the same naming convention.pup is executable: run pup --version via Bash. A JSON response containing "version" confirms pup is available."Neither the Datadog MCP server nor the pup CLI is available. Connect the MCP server (
claude mcp add --scope user --transport http datadog-llmo-mcp 'https://mcp.datadoghq.com/api/unstable/mcp-server/mcp?toolsets=llmobs') or install pup."
--backend pup is accepted anywhere in the invocation arguments and is stripped before passing remaining args to the skill logic.
pup invocation rules:
pup llm-obs <subcommand> [flags][{"type": "text", "text": "<json>"}]).pup auth login and stop.Invocation ID: At the very start of each invocation, before any MCP tool call, generate an 8-character hex invocation ID (e.g., 3a9f1c2b). Keep it constant for the entire invocation.
Intent tagging: On every MCP tool call, prefix telemetry.intent with skill:llm-obs-experiment-analyzer[<inv_id>] — followed by a description of why the tool is being called. On the first MCP tool call only, use skill:llm-obs-experiment-analyzer:start[<inv_id>] — instead (note the :start suffix). Example first call: skill:llm-obs-experiment-analyzer:start[3a9f1c2b] — Phase 1: get experiment summary to orient analysis
Analyzes one or two LLM experiments. Supports four modes based on inputs:
| Inputs | Mode |
|---|---|
| 2 IDs, no question | Comparative Exploratory |
| 2 IDs + question | Comparative Q&A |
| 1 ID, no question | Single Exploratory |
| 1 ID + question | Single Q&A |
Arguments: $ARGUMENTS
Note: Tool names below are shown with an example
mcp__<server>__prefix. The actual prefix depends on the environment and MCP server name — it may differ or be absent entirely. Always call tools using the exact name visible in your active tool list (see Backend Detection step 3).
| Tool | Purpose |
|---|---|
mcp__datadog-llmo-mcp__get_llmobs_experiment_summary | Get total events, error count, metrics stats, available dimensions |
mcp__datadog-llmo-mcp__list_llmobs_experiment_events | Query events with filters, sorting, pagination |
mcp__datadog-llmo-mcp__get_llmobs_experiment_event | Get full event details (input, output, expected_output, metrics) |
mcp__datadog-llmo-mcp__get_llmobs_experiment_metric_values | Get metric stats overall and segmented by dimension. Use segment_by_dimension (not segment_dimension) to segment; optionally segment_dimension_value to filter to a specific value. |
mcp__datadog-llmo-mcp__get_llmobs_experiment_dimension_values | List unique values for a dimension with counts |
mcp__datadog-mcp-core__create_datadog_notebook | Export report as a Datadog notebook |
Parse $ARGUMENTS:
--output agent|file|notebook flag if present.Mode determination:
Output mode determination:
If --output was provided in arguments, use that mode and skip asking.
Otherwise, ask two separate sequential AskUserQuestion calls before proceeding — never combined into a single call:
--output was not specified, ask where to deliver the report (chat, file, or Datadog notebook). Always ask this as its own standalone call.Output modes:
evals/reports/YYYY-MM-DD-<experiment-slug>-analysis.md
Present it to the user and let them confirm or adjust. Then proceed.mcp__datadog-mcp-core__create_datadog_notebook at the end. In pup mode, use pup notebooks create --title "TITLE" --file /tmp/nb_cells.json instead (see Tool Reference). If neither MCP nor pup is available, output these setup instructions instead of failing:
After resolving mode and output, proceed to Phase 1. There will be one additional AskUserQuestion interaction at Phase 1.5 before the deep analysis begins.
Comparative: Call get_llmobs_experiment_summary for both experiments. Produce a side-by-side comparison:
When error_count > 0, call get_llmobs_experiment_dimension_values for error_type and report the breakdown by exception class (e.g. "2 errors: asyncio.exceptions.cancellederror"). Errors mean the executor threw an unhandled exception — no eval scores were produced for those samples. Do not report a percentage; report the count and type(s).
Single: Call get_llmobs_experiment_summary for the experiment. Determine:
error_type breakdown if non-zero)metric_type as returned by the summary (score, boolean, categorical). Do not infer semantic groupings or categories from label name patterns or prefixes — the label string is not a reliable signal for what a metric measures.After completing Phase 1, run the following three steps before any AskUserQuestion.
Step 1 — Classify every metric using summary statistics only (no additional tool calls):
| Class | Condition | Meaning |
|---|---|---|
always_zero | max == 0 | Feature disabled or not implemented — no signal |
perfect | min == 1 | Always passes — no diagnostic signal |
saturated | mean ≥ 0.99 and min < 1 | Rarely fails — low diagnostic value |
struggling | mean < 0.70 | Meaningful failure rate — highest diagnostic value |
interesting | 0.70 ≤ mean < 0.99 and min < max | Partial failures — moderate diagnostic value |
Step 2 — Print the full metric table to chat before asking any question. This gives the user complete visibility — never truncated by option limits. Format:
Flag any always_zero metrics with a note — e.g. "N metrics always score 0 and appear to be disabled features; they will be excluded from suggested groupings."
Step 3 — AskUserQuestion with options built entirely from the computed classes:
Generate options dynamically based on what is actually present in the data. Do not invent option names from label prefixes.
open_answer 0.33, c_permanence 0.68"). This is the grounded suggestion — based on observed pass rates, not label names. If there are no struggling metrics, replace this option with "Lowest-performing metrics (N)" covering the bottom N by mean.If the user selects "A specific metric", ask a second AskUserQuestion that shows the 4 metrics with the lowest mean as labeled options (label = metric name, description = mean: X.XX — class). In the question text, explicitly say: "Or type any metric name from the table above into 'Other'." The always_zero and perfect metrics must not appear in the 4 options (they have no diagnostic value); restrict the 4 to struggling and interesting classes only. After the user picks one, restrict all analysis in Phases 2–4 to that single metric only.
Scope enforcement:
get_llmobs_experiment_metric_values for any metric outside the selection.Comparative: Using only the metrics selected in Phase 1.5 (intersected with shared metrics) and shared dimensions, identify:
Generate Datadog comparison UI links:
https://app.datadoghq.com/llm/experiment-comparisonbaselineExperimentId, experimentIds (candidate%2Cbaseline), tableView=allproject, compareDatasetId, selectedEvaluationselectedEvaluation priority: overall/overall_score/rubric metric → primary metric → first shared metricSingle: Measure per-metric performance across all dimensions for only the metrics selected in Phase 1.5. Identify:
Generate Datadog experiment UI link:
https://app.datadoghq.com/llm/experiments/{experiment_id}Run all necessary deep dives automatically. Do not ask for approval or pause. Scope all deep dives strictly to the metrics selected in Phase 1.5 — do not call get_llmobs_experiment_metric_values for any metric outside the selection.
Q&A modes: Focus deep dives on what is needed to answer the question directly. Pull specific samples, segment by relevant dimensions, inspect examples.
Exploratory modes: Investigate the most interesting signals broadly:
Rules:
For each sampled event, generate a direct span link:
https://app.datadoghq.com/llm/experiments/{experiment_id}?selectedTab=overview&sp=[{"p":{"experimentId":"{experiment_id}","spanId":"{span_id}"},"i":"experiment-details"}]&spanId={span_id}
The sp query parameter value must be percent-encoded before rendering (e.g. [ → %5B, { → %7B, " → %22, } → %7D, ] → %5D).
For each Deep Dive segment, generate a direct link to view those samples in the (candidate) experiment:
https://app.datadoghq.com/llm/experiments/{experiment_id}?selectedTab=overview&filter[{dimension}]={value}
If you are not confident the filter URL format works for this dimension, omit the filter params and link to the experiment root instead. Never fabricate filter URLs.
Comparative Exploratory:
Comparative Q&A:
Single Exploratory:
Single Q&A:
All modes: open with a one-line issue type tally — e.g. "3 agent issues, 1 evaluator/dataset issue, 1 ambiguous" — before the detailed findings. Use quantified deltas/rates wherever possible. Redact PII.
Always produce both ## Summary & Recommendations and ## Synthesis sections regardless of experiment complexity, how many metrics exist, or how quickly the answer is apparent. Do not skip Summary because the findings are simple or obvious. Do not skip Synthesis because you've already covered the findings in Deep Dives. These two sections are the most portable output of the analysis — they are what a reader encounters first and last.
Agent: Present the full report in the conversation using the report format below.
File: Write the report to the pre-confirmed path. Confirm with: "Report saved to <path>."
Notebook: Call mcp__datadog-mcp-core__create_datadog_notebook with the following parameters:
name (by mode):
| Mode | Name |
|---|---|
| Comparative Exploratory | Experiment Analysis: {baseline_short} (Baseline) vs {candidate_short} (Candidate) — YYYY-MM-DD |
| Comparative Q&A | Experiment Q&A: {baseline_short} vs {candidate_short} — YYYY-MM-DD |
| Single Exploratory | Experiment Analysis: {experiment_short} — YYYY-MM-DD |
| Single Q&A | Experiment Q&A: {experiment_short} — YYYY-MM-DD |
where short = first 8 characters of the UUID. |
cells: one cell per report section — do NOT put the entire report in a single cell. Structure:
### subheaders: Experiment (link + executive summary), Key Findings (bullets), Recommendations (numbered list) — always present, always first, never skipped regardless of experiment complexityOmit the # Experiment Analysis Report top-level heading from all cells — it is already shown as the notebook title.
time: { "live_span": "1h" }
After the notebook is created, output the URL in chat: "Report exported to notebook: <url>"
If the tool is unavailable, follow the fallback instructions in Phase 0.
After delivering the report, append a follow-up section:
Stay active after the report. Answer follow-up questions using the same MCP tools, referencing findings already gathered. Do not re-run analyses you've already performed unless new questions require it.
Link rules:
https://app.datadoghq.com/llm/experiments/{full_uuid}.[Baseline \{short_id}`]({baseline_url})andCandidate `{short_id}` [blocked]`. This makes the full header cell clickable, not just the ID portion.This appendix applies only in pup mode. In MCP mode, use the tool names in the workflow sections directly.
| MCP Tool | pup Command |
|---|---|
get_llmobs_experiment_summary(experiment_id) | pup llm-obs experiments summary EXPERIMENT_ID |
list_llmobs_experiment_events(experiment_id, ...) | pup llm-obs experiments events list EXPERIMENT_ID [--filter-metric-label L] [--sort-by-metric M] [--sort-direction asc|desc] [--limit N] — confirm filter/sort flag names with pup llm-obs experiments events list --help before use |
get_llmobs_experiment_event(experiment_id, event_id) | pup llm-obs experiments events get EXPERIMENT_ID EVENT_ID |
get_llmobs_experiment_metric_values(experiment_id, metric_label, ...) | pup llm-obs experiments metric-values EXPERIMENT_ID --metric-label L [--segment-by-dimension D] [--segment-dimension-value V] |
get_llmobs_experiment_dimension_values(experiment_id, dimension_key) | pup llm-obs experiments dimension-values EXPERIMENT_ID --dimension-key K |
| MCP Tool | pup Command |
|---|---|
create_datadog_notebook(name, cells, ...) | pup notebooks create --title "TITLE" --file /tmp/nb_cells.json — confirm exact flags with pup notebooks create --help |
The cells file is a JSON array of cell objects: