Skip to main content

Feedback

Feedback records capture human-in-the-loop signals such as thumbs, ratings, comments, and corrections. Use feedback when you need to attach user, QA, Studio, or system review data to a trace or span and query that data alongside other observability signals.

Unlike metrics and scores, feedback is usually supplied by a person or review workflow. Numeric feedback values can be aggregated, grouped, charted over time, and queried for percentiles.

When to use feedback
Direct link to When to use feedback

  • Collect user satisfaction ratings for agent responses.
  • Store QA comments or corrections next to the trace they review.
  • Build dashboards for ratings by agent, environment, or experiment.

Add feedback
Direct link to Add feedback

Use mastra.observability.addFeedback() when you want to annotate a persisted trace or span from app code. The helper is optional on the observability entrypoint, so check that the active observability implementation supports it. See the client SDK observability reference for all client methods.

src/mastra/feedback.ts
if (!mastra.observability.addFeedback) {
throw new Error('Feedback is not supported by the active observability implementation')
}

await mastra.observability.addFeedback({
traceId: 'trace-123',
spanId: 'span-456',
feedback: {
feedbackSource: 'user',
feedbackType: 'rating',
value: 1,
comment: 'The answer solved my issue.',
},
})

When you pass only traceId and spanId, addFeedback() rehydrates the target from configured observability storage before emitting the feedback event. Configure MastraStorageExporter when feedback is added after the traced execution has finished. If the trace isn't available in storage, Mastra logs a warning and drops the feedback event.

During a live traced execution, you can pass its correlationContext to emit feedback without rehydrating the trace from storage. This path is useful when the active request collects feedback before its tracing context ends.

Find the trace for a message
Direct link to Find the trace for a message

Feedback is usually collected against a message a user has already read, so you need the traceId for that message. Assistant messages carry it in content.metadata, both in the stream result and when the message is recalled later from memory:

src/mastra/message-trace.ts
const agent = mastra.getAgent('weatherAgent')
const memory = await agent.getMemory()

const { messages } = await memory!.recall({ threadId, perPage: false })

const message = messages.find(m => m.id === messageId)
const traceId = message?.content.metadata?.traceId

The value is the same trace the run reports as traceId on its result, so feedback collected at generation time and feedback collected later against a stored message anchor to the same trace. Messages produced while tracing is disabled have no traceId.

Create feedback
Direct link to Create feedback

Every createFeedback() requires feedbackType and value. Add traceId or spanId when the feedback should be anchored to a trace or a specific span. Use feedbackSource as optional string metadata, such as user, qa, studio, or system.

For storage-level writes, include timestamp because the method writes directly to the store.

const observability = await mastra.getStorage()!.getStore('observability')

await observability!.createFeedback({
feedback: {
feedbackId: 'feedback-rating-1',
timestamp: new Date(),
traceId: 'trace-123',
spanId: 'span-456',
feedbackSource: 'user',
feedbackType: 'rating',
value: 1,
comment: 'The answer solved my issue.',
tags: ['production'],
},
})

await observability!.createFeedback({
feedback: {
feedbackId: 'feedback-comment-1',
timestamp: new Date(),
feedbackSource: 'qa',
feedbackType: 'comment',
value: 'Needs a citation before shipping.',
experimentId: 'support-agent-eval',
},
})

List feedback
Direct link to List feedback

Use listFeedback() to page through raw records or poll with delta mode.

const result = await observability!.listFeedback({
filters: {
feedbackType: 'rating',
feedbackSource: 'user',
timestamp: { start: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) },
},
pagination: { page: 0, perPage: 20 },
orderBy: { field: 'timestamp', direction: 'DESC' },
})

console.log(result.feedback, result.pagination?.hasMore)

Filters include target fields such as traceId and spanId, feedback fields such as feedbackType, feedbackSource, and feedbackUserId, and shared context fields such as entityName, environment, experimentId, and tags. See FeedbackFilter for every filter field. Over HTTP, the same fields are passed as query parameters, for example GET /api/observability/feedback?traceId=trace-123&environment=production. See list query parameters.

await observability!.listFeedback({
filters: {
traceId: 'trace-123',
feedbackType: ['rating', 'thumbs'],
tags: ['production'],
},
})

Delete feedback
Direct link to Delete feedback

Use deleteFeedback() to delete feedback records by id. This is useful when a comment contains sensitive data. Each request accepts at most 1,000 feedbackIds, and deletion is idempotent. The optional organizationId field filters by organization, and the optional resourceId field filters by resource. You can supply either filter independently or use both together. In Studio, each comment on a trace or span Feedback tab has a delete action. Deleting a trace with deleteTraces() also deletes the feedback linked to it. See Deleting traces.

await observability!.deleteFeedback({
feedbackIds: ['feedback-1', 'feedback-2'],
})

Deleted records also disappear from feedback analytics. ClickHouse uses a lightweight delete to hide rows without guaranteeing immediate physical removal, so open-source deployments must configure an observability retention period to physically purge them. Each request is marked applied once its delete succeeds; if the delete fails, the request stays unapplied, doesn't block updates to the still-visible feedback, and you retry by calling the delete API again. On ClickHouse, the next review update also retries the delete and reports the feedback as not found if the retry succeeds, or saves the new status and returns the delete's error if it fails again. Open-source deployments have no background reconciler. Configure retention for every observability signal to expire deletion requests after the signal rows they protect. If any signal is unbounded, deletion requests also remain unbounded to prevent deleted data from being reintroduced. On ClickHouse, delete APIs leave the separate delta cursor table untouched. Its rows contain identifiers rather than feedback payloads and expire within two days.

Query feedback analytics
Direct link to Query feedback analytics

OLAP feedback queries operate on numeric value fields. Use them for ratings, thumbs encoded as 1 and -1, numeric QA scores, or other numeric feedback types.

const rating = await observability!.getFeedbackAggregate({
feedbackType: 'rating',
feedbackSource: 'user',
aggregation: 'avg',
comparePeriod: 'previous_day',
})

const byAgent = await observability!.getFeedbackBreakdown({
feedbackType: 'rating',
groupBy: ['entityName'],
aggregation: 'avg',
filters: { environment: 'production' },
})

const ratingsOverTime = await observability!.getFeedbackTimeSeries({
feedbackType: 'rating',
aggregation: 'avg',
interval: '1h',
groupBy: ['feedbackSource'],
})

See the feedback reference for all fields, filters, return types, and percentile query parameters.

The local runtime exposes the list route at /api/observability/feedback and analytics under its related paths. See the HTTP routes table and list query parameters. Mastra Platform provides a separate, unversioned hosted query API. See the Feedback API for regional endpoints, authentication, and project scoping.

Export feedback to Mastra Platform
Direct link to Export feedback to Mastra Platform

MastraPlatformExporter forwards emitted feedback events to Mastra Platform automatically because the hosted query API doesn't provide a creation route.

If your application adds feedback after an agent or workflow response using only its traceId, configure MastraStorageExporter alongside MastraPlatformExporter. The storage exporter keeps the trace available for addFeedback() to rehydrate, while the Platform exporter forwards the resulting feedback event without adding the trace to the application's local storage.

See Observability on Mastra Platform for the combined exporter configuration.

Export feedback to external platforms
Direct link to Export feedback to external platforms

Feedback flows through the observability event bus, so exporters that support feedback forward it automatically. The PostHog exporter sends feedback as native $ai_feedback events that appear on the linked trace in PostHog.