Skip to main content
Latitude doesn’t ship a dashboard builder. Instead it exposes one composable analytics tool, queryAnalytics, over the MCP connection. You ask your agent for a dashboard; the agent makes a handful of queryAnalytics calls and renders a self-contained HTML page from the results. Latitude provides the data; the agent renders it.

The shape of every widget

Every chart is the same three-part query over a filtered stream:
  • Metric — the number each row reduces to (count, errorRate, percentile(duration), avg(cost), …).
  • Breakdown — the dimension to split across, one row per value (by model, tool, signalId, …).
  • Time bucket — optional granularity (hour / day / week) that turns a number into a trend.
So “error rate by model, weekly” = metric errorRate × breakdown model × bucket week. Swap any axis for the next chart — no new endpoint.

Streams and metrics

StreamWhat it measuresMetricsBreak down by
traces / sessions / spansrequests, conversations, operationscount, errorRate, cacheHitRate, sum/min/max/avg/median of duration|cost|tokens, plus percentile ({ kind: "percentile", field, p }, p 1–99)model, provider, service, tool, tag, status (+ name/userId on traces, operation on spans)
scores (signals)scored occurrencescount, passRate, errorRate, avg/min/max/median of valuesignalId, source, model, provider, service, tool, tag
behaviorsclustered behaviorscount, avg/min/max/median of confidencecluster, session, method
momentslabeled conversation momentscount, avg/min/max/median of confidence|coherencekind, actor, session
Values are returned in human units: duration in seconds, cost in dollars, and rates (errorRate, cacheHitRate, passRate) as a 0–1 fraction.

Example calls

// Error rate by model, weekly, last 30 days
queryAnalytics({
  stream: "traces", metric: { kind: "errorRate" }, breakdown: "model",
  timeBucket: { unit: "week" },
  range: { fromIso: "2026-05-31T00:00:00Z", toIso: "2026-06-30T00:00:00Z" }
})

// 95th-percentile latency by tool
queryAnalytics({
  stream: "traces", metric: { kind: "percentile", field: "duration", p: 95 }, breakdown: "tool",
  range: { fromIso: "2026-06-23T00:00:00Z", toIso: "2026-06-30T00:00:00Z" }
})

// Cost by provider
queryAnalytics({
  stream: "traces", metric: { kind: "sum", field: "cost" }, breakdown: "provider",
  range: { fromIso: "2026-06-23T00:00:00Z", toIso: "2026-06-30T00:00:00Z" }
})

// Conversation fallout: moment count by kind
queryAnalytics({
  stream: "moments", metric: { kind: "count" }, breakdown: "kind",
  range: { fromIso: "2026-06-23T00:00:00Z", toIso: "2026-06-30T00:00:00Z" }
})
Each returns a tidy series — [{ key?, bucketStart?, value }] — small enough to embed inline no matter how many traces it spans.

Row-level widgets

For a “top 10 slowest tool calls” style table (rows, not an aggregate), use querySpans to list the individual spans across traces:
querySpans({
  filters: { operation: [{ op: "eq", value: "execute_tool" }] },
  range: { fromIso: "2026-06-23T00:00:00Z", toIso: "2026-06-30T00:00:00Z" },
  limit: 10
})

Rendering

The agent embeds the returned series directly into a self-contained HTML file (an inline chart library, data baked in) and commits it to your repo or hosts it wherever you like. Nothing is rendered by Latitude — which keeps the data portable and the setup dependency-free. A good prompt is simply: “Build a self-contained HTML dashboard of this project’s LLM reliability and cost over the last 30 days.” The agent discovers the right queryAnalytics calls on its own. See Agent dispatch for the investigation counterpart — how an agent uses these same tools to root-cause a signal.