You can now capture human feedback — thumbs, ratings, comments, or corrections — on any agent response. Tag each record by source (user, SME, or qa) so you can visualize, filter and compare later.
Every feedback record can be anchored to a threadId, traceId, or spanId. Reviewers can see agent responses in the context of a conversation and grade each one accordingly.
Feedback records can be queried per agent — average rating, thumb split, or comment volume. Compare user ratings versus QA ratings, and open the trace in Studio to inspect the model calls, tool calls, and outputs behind any feedback record.
Before feedback, ratings and reviewer comments lived in external storage with no link back to the trace that produced them. Now they sit alongside spans, metrics, and logs in the same observability store, and can be forwarded to PostHog, Braintrust, Arize, and other supported exporters.
Get started
Install Mastra observability and and storage backends for memory and observability:
npm install @mastra/core @mastra/observability @mastra/libsql @mastra/duckdbRequires @mastra/core@1.18.0 or later, added in PR #14842.
Feedback lives in the observability domain. The example below routes the observability domain to DuckDB with default storage on LibSQL using a MastraCompositeStore:
import { Mastra } from "@mastra/core/mastra";
import { MastraCompositeStore } from "@mastra/core/storage";
import { LibSQLStore } from "@mastra/libsql";
import { DuckDBStore } from "@mastra/duckdb";
import { Observability, MastraStorageExporter } from "@mastra/observability";
export const mastra = new Mastra({
agents: {
/* ... */
},
storage: new MastraCompositeStore({
id: "composite-storage",
default: new LibSQLStore({ url: "file:./mastra.db" }),
domains: {
observability: await new DuckDBStore().getStore("observability")
}
}),
observability: new Observability({
configs: {
default: {
serviceName: "mastra-feedback",
exporters: [new MastraStorageExporter()]
}
}
})
});Capturing feedback
Feedback records are available via mastra.observability, which publishes events through Mastra's observability pipeline: your span processors run, the record is enriched with context pulled from the trace, and every exporter you've configured receives the record.
import { mastra } from "../src/mastra";
if (!mastra.observability.addFeedback) {
throw new Error("Feedback not supported.");
}
await mastra.observability.addFeedback({
traceId,
feedback: {
feedbackType: "rating",
value: 4,
feedbackSource: "qa",
comment: "Solid answer, would ship as-is.",
metadata: { messageId }
}
});List feedback
Read raw feedback records back from the store. Filter by any anchor or context field — threadId, traceId, spanId, feedbackType, feedbackSource, entityName — and paginate.
const { feedback } = await observability!.listFeedback({
filters: { threadId },
pagination: { perPage: 100 }
});Query analytics
Numeric feedback (rating, or thumbs coded as 1/-1) queries through the same OLAP surface as metrics. Aggregate with an optional period-over-period comparison:
const avg = await observability!.getFeedbackAggregate({
feedbackType: "rating",
aggregation: "avg",
comparePeriod: "previous_week"
});
// → { value: 4.2, previousValue: 3.8, changePercent: 10.5 }Break down by any dimension — feedbackSource, entityName, environment:
const bySource = await observability!.getFeedbackBreakdown({
feedbackType: "rating",
groupBy: ["feedbackSource"],
aggregation: "avg"
});
// → { groups: [{ dimensions: { feedbackSource: "admin" }, value: 4.1 }, ...] }Bucket by interval for time series:
const overTime = await observability!.getFeedbackTimeSeries({
feedbackType: "rating",
interval: "1h",
aggregation: "avg"
});
// → { series: [{ name: "rating", points: [{ timestamp, value }, ...] }] }Same shape for getFeedbackPercentiles({ percentiles: [0.5, 0.95], interval: "1d" }).
From the browser
Every method above (excluding addFeedback) is also available using the Mastra client SDK:
import { MastraClient } from "@mastra/client-js";
export const client = new MastraClient({ baseUrl: "http://localhost:4111" });
const { feedback } = await client.listFeedback({
filters: { threadId },
pagination: { perPage: 100 }
});For more information and full configuration options, see:
