Skip to content

feat: add persisted feedback-provided markers for message feedback me…#1059

Merged
omeraplak merged 5 commits intomainfrom
feat/feedback-status-persist
Feb 13, 2026
Merged

feat: add persisted feedback-provided markers for message feedback me…#1059
omeraplak merged 5 commits intomainfrom
feat/feedback-status-persist

Conversation

@omeraplak
Copy link
Member

@omeraplak omeraplak commented Feb 12, 2026

…tadata

PR Checklist

Please check if your PR fulfills the following requirements:

Bugs / Features

What is the current behavior?

What is the new behavior?

fixes (issue)

Notes for reviewers


Summary by cubic

Adds persisted “feedback provided” markers to assistant message metadata and returns a feedback handle from generate/stream results so feedback UIs stay hidden after reloads. Updates server schema, exported types, docs, and tests.

  • New Features

    • AgentFeedbackMetadata now includes provided, providedAt, and feedbackId.
    • result.feedback is an AgentFeedbackHandle with isProvided() and markFeedbackProvided(...).
    • Agent helpers: Agent.isFeedbackProvided(...), Agent.isMessageFeedbackProvided(...), and agent.markFeedbackProvided(...) to persist state on stored messages.
    • Server response schema now includes the new feedback fields.
  • Migration

    • After feedback ingestion succeeds, call result.feedback.markFeedbackProvided(...) or agent.markFeedbackProvided(...).
    • Use only with memory-backed conversations (userId and conversationId).
    • In UI, hide feedback controls when provided/providedAt/feedbackId is present.

Written for commit a25ea6a. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Persist feedback state so submitted feedback stays hidden after reloads; new agent.markFeedbackProvided(...) and result.feedback.markFeedbackProvided(...)
    • New helpers to check feedback state (isProvided(), Agent.isFeedbackProvided(), Agent.isMessageFeedbackProvided())
  • API / Types

    • Feedback metadata extended with provided, providedAt, and feedbackId; feedback handle type added
  • Documentation

    • Updated guides with examples and best practices for persisting and managing feedback state
  • Tests

    • Comprehensive tests covering feedback helpers, persistence, and handle behavior

@changeset-bot
Copy link

changeset-bot bot commented Feb 12, 2026

🦋 Changeset detected

Latest commit: a25ea6a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@voltagent/core Patch
@voltagent/server-core Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@joggrbot

This comment has been minimized.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 12, 2026

📝 Walkthrough

Walkthrough

Adds persisted feedback metadata (provided, providedAt, feedbackId), new feedback helpers and AgentFeedbackHandle, agent.markFeedbackProvided to persist provided state to memory, server schema updates, tests, and docs demonstrating client/server flows for marking feedback provided.

Changes

Cohort / File(s) Summary
Feedback System Core
packages/core/src/agent/feedback.ts
New module: detection helpers, message lookup, input validation, memory-backed markFeedbackProvided persistence flow, and createFeedbackHandle factory producing AgentFeedbackHandle with isProvided and markFeedbackProvided.
Agent Integration
packages/core/src/agent/agent.ts
Exposes static helpers isFeedbackProvided, isMessageFeedbackProvided, and instance markFeedbackProvided. Wraps raw metadata into AgentFeedbackHandle and surfaces handle on generate/stream results.
Types & Public Exports
packages/core/src/agent/types.ts, packages/core/src/index.ts
Adds provided, providedAt, feedbackId to AgentFeedbackMetadata; introduces AgentFeedbackHandle, AgentFeedbackMarkProvidedInput, and AgentMarkFeedbackProvidedInput; exports new types.
Tests
packages/core/src/agent/feedback.spec.ts
New comprehensive tests for detection logic, message lookup, memory persistence, feedback handle behavior, and error paths.
Server Schema
packages/server-core/src/schemas/agent.schemas.ts
Adds optional provided, providedAt, and feedbackId fields to feedback object in TextResponseSchema.
Docs & Observability
website/docs/agents/message-types.md, website/docs/agents/overview.md, website/observability/feedback.md
Documents expanded UI message feedback fields, result.feedback helpers (isProvided, markFeedbackProvided), client/server submission example, and guidance to persist provided state with agent.markFeedbackProvided.
Changesets
.changeset/friendly-feedback-state.md, .changeset/friendly-feedback-world.md
Release notes summarizing feedback persistence features, API additions, and usage examples.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Client
    participant Agent
    participant Memory
    participant FeedbackURL as ExternalFeedbackEndpoint

    User->>Client: click "submit feedback"
    Client->>FeedbackURL: POST feedback payload
    FeedbackURL-->>Client: 200 OK
    Client->>Agent: call result.feedback.markFeedbackProvided(feedbackId?)
    Agent->>Memory: load conversation & message by ids
    Agent->>Memory: update message.metadata.feedback { provided, providedAt, feedbackId }
    Memory-->>Agent: confirm persisted metadata
    Agent-->>Client: resolve updated AgentFeedbackMetadata / handle updated
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • lzj960515

Poem

🐰
I nudged a flag with a tiny paw,
Marked "provided" so the UI saw,
Timestamps tucked in a gentle row,
Memory keeps what the users show,
Hooray for carrots and tidy flow 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 2
❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The PR description includes a cubic-generated summary covering features, migration guidance, and implementation details; however, critical template sections (current behavior, new behavior, related issues, and checklist items) remain incomplete or unchecked. Complete the template sections: fill 'What is the current behavior?' and 'What is the new behavior?', link related issue(s), explicitly verify tests/docs/changesets are added, and confirm commit message compliance.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main feature: adding persisted feedback-provided markers for message feedback metadata, which aligns with the core changes across the codebase.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/feedback-status-persist

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 10 files

@cloudflare-workers-and-pages
Copy link

cloudflare-workers-and-pages bot commented Feb 12, 2026

Deploying voltagent with  Cloudflare Pages  Cloudflare Pages

Latest commit: a25ea6a
Status: ✅  Deploy successful!
Preview URL: https://bd162f7e.voltagent.pages.dev
Branch Preview URL: https://feat-feedback-status-persist.voltagent.pages.dev

View logs

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@packages/core/src/agent/feedback.ts`:
- Around line 110-121: The code always overwrites providedAt by calling
resolveFeedbackProvidedAt(input.providedAt); instead, preserve the existing
timestamp unless the caller explicitly supplies one: compute providedAt by
checking if input.providedAt is defined (use
resolveFeedbackProvidedAt(input.providedAt)), otherwise reuse
existingFeedback.providedAt (fall back to resolveFeedbackProvidedAt(undefined)
only if there is no existing value). Update the assignment that builds
updatedFeedback (and any uses in markFeedbackProvided) to use this conditional
logic so repeated calls don't clobber the original providedAt.

In `@website/docs/agents/overview.md`:
- Around line 155-163: The example uses an undeclared variable `feedbackId` in
the `result.feedback.markFeedbackProvided({ feedbackId })` call; update the
snippet to either declare `feedbackId` or add an inline comment explaining its
origin (for example: `// feedbackId returned from feedback ingestion response`)
so readers know where to get it; modify the
`result.feedback.markFeedbackProvided` example to include that comment or a
brief note that `feedbackId` comes from the feedback ingestion API response.

In `@website/observability/feedback.md`:
- Around line 106-112: The code examples reference undeclared variables
(feedbackId, agentId, userId, conversationId) which will cause ReferenceErrors;
update the examples around result.feedback.markFeedbackProvided and
submitFeedback to show where these values come from (e.g., add a short preamble
or inline comment indicating they are provided by your app/context) and ensure
the sample shows placeholder declarations for feedbackId, agentId, userId, and
conversationId so readers can copy the snippets without errors.
🧹 Nitpick comments (2)
website/observability/feedback.md (1)

268-276: JSON.stringify used in client-side documentation example.

The coding guidelines state to use safeStringify from @voltagent/internal instead of JSON.stringify. However, this is a client-side browser fetch example in a Markdown doc where @voltagent/internal may not be available. If this example is intended for server-side usage, consider using safeStringify. If it's genuinely client-side, a brief note clarifying that safeStringify is preferred for server-side code would help maintain consistency with project conventions.

As per coding guidelines: "Never use JSON.stringify; use the safeStringify function instead, imported from @voltagent/internal"

packages/core/src/agent/feedback.spec.ts (1)

14-41: Consider adding a test case for feedbackId-only detection.

isFeedbackProvided has three detection paths (provided, providedAt, feedbackId), but only two are tested here. The feedbackId-only case is missing:

💡 Suggested additional test case
     expect(
       isFeedbackProvided({
         traceId: "trace-1",
         key: "satisfaction",
         url: "https://example.com/fb",
         providedAt: "2026-02-12T00:00:00.000Z",
       }),
     ).toBe(true);
+
+    expect(
+      isFeedbackProvided({
+        traceId: "trace-1",
+        key: "satisfaction",
+        url: "https://example.com/fb",
+        feedbackId: "fb-123",
+      }),
+    ).toBe(true);
   });

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@packages/core/src/agent/feedback.ts`:
- Around line 154-158: The code currently calls .trim() on feedback.traceId,
feedback.key, and feedback.url which can throw if any are undefined; update the
extraction to guard like the tokenId logic: use typeof checks (e.g., typeof
feedback.traceId === "string" ? feedback.traceId.trim() : "") or default to ""
for feedbackTraceId, feedbackKey, and feedbackUrl (or validate and throw a clear
error before trimming) so trimming is only done on strings and runtime
TypeErrors are avoided.
🧹 Nitpick comments (2)
packages/core/src/agent/feedback.ts (1)

249-258: Consider Object.assign instead of manual field-by-field copy.

All enumerable properties of updated are copied onto feedbackHandle one by one. Object.assign(feedbackHandle, updated) achieves the same result more concisely and avoids needing to update this block when new fields are added to AgentFeedbackMetadata.

♻️ Proposed simplification
      if (updated) {
-        feedbackHandle.traceId = updated.traceId;
-        feedbackHandle.key = updated.key;
-        feedbackHandle.url = updated.url;
-        feedbackHandle.tokenId = updated.tokenId;
-        feedbackHandle.expiresAt = updated.expiresAt;
-        feedbackHandle.feedbackConfig = updated.feedbackConfig;
-        feedbackHandle.provided = updated.provided;
-        feedbackHandle.providedAt = updated.providedAt;
-        feedbackHandle.feedbackId = updated.feedbackId;
+        Object.assign(feedbackHandle, updated);
      }
website/observability/feedback.md (1)

275-311: Documentation examples use JSON.stringify; consider using safeStringify for consistency.

The submitFeedback (line 278) and markFeedbackProvided (line 309) examples use JSON.stringify. While this .md file isn't directly subject to the **/*.ts guideline, users will copy these snippets into their TypeScript code. Consider using safeStringify from @voltagent/internal in the examples to promote the project's convention and avoid users introducing JSON.stringify into their code. As per coding guidelines, "Never use JSON.stringify; use the safeStringify function instead, imported from @voltagent/internal".

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 1 file (changes from recent commits).

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them.


<file name="packages/core/src/agent/feedback.ts">

<violation number="1" location="packages/core/src/agent/feedback.ts:156">
P2: Defaulting missing feedback identifiers to "" can cause false matches: if feedback lacks tokenId/traceId/key/url, the empty-string comparison will match any message missing those fields. Use undefined (or non-empty checks) so matching only occurs when identifiers are present.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Comment on lines +156 to +158
const feedbackTraceId = typeof feedback.traceId === "string" ? feedback.traceId.trim() : "";
const feedbackKey = typeof feedback.key === "string" ? feedback.key.trim() : "";
const feedbackUrl = typeof feedback.url === "string" ? feedback.url.trim() : "";
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 13, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Defaulting missing feedback identifiers to "" can cause false matches: if feedback lacks tokenId/traceId/key/url, the empty-string comparison will match any message missing those fields. Use undefined (or non-empty checks) so matching only occurs when identifiers are present.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/core/src/agent/feedback.ts, line 156:

<comment>Defaulting missing feedback identifiers to "" can cause false matches: if feedback lacks tokenId/traceId/key/url, the empty-string comparison will match any message missing those fields. Use undefined (or non-empty checks) so matching only occurs when identifiers are present.</comment>

<file context>
@@ -153,9 +153,9 @@ export function findFeedbackMessageId(
-  const feedbackTraceId = feedback.traceId.trim();
-  const feedbackKey = feedback.key.trim();
-  const feedbackUrl = feedback.url.trim();
+  const feedbackTraceId = typeof feedback.traceId === "string" ? feedback.traceId.trim() : "";
+  const feedbackKey = typeof feedback.key === "string" ? feedback.key.trim() : "";
+  const feedbackUrl = typeof feedback.url === "string" ? feedback.url.trim() : "";
</file context>
Suggested change
const feedbackTraceId = typeof feedback.traceId === "string" ? feedback.traceId.trim() : "";
const feedbackKey = typeof feedback.key === "string" ? feedback.key.trim() : "";
const feedbackUrl = typeof feedback.url === "string" ? feedback.url.trim() : "";
const feedbackTraceId =
typeof feedback.traceId === "string" && feedback.traceId.trim().length > 0
? feedback.traceId.trim()
: undefined;
const feedbackKey =
typeof feedback.key === "string" && feedback.key.trim().length > 0
? feedback.key.trim()
: undefined;
const feedbackUrl =
typeof feedback.url === "string" && feedback.url.trim().length > 0
? feedback.url.trim()
: undefined;
Fix with Cubic

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@packages/core/src/agent/feedback.ts`:
- Around line 190-196: The current equality check using messageTraceId ===
feedbackTraceId && messageKey === feedbackKey && messageUrl === feedbackUrl can
false-positive when all three feedback fallbacks are empty strings; add a
short-circuit guard before this check that skips matching if feedbackTraceId,
feedbackKey, and feedbackUrl are all empty/blank (or falsy), e.g., require at
least one non-empty feedback field before returning message.id so you don't
accidentally match messages with missing/empty feedback fields.
🧹 Nitpick comments (2)
packages/core/src/agent/feedback.ts (1)

249-258: Fragile manual field-copy — consider Object.assign to stay in sync with AgentFeedbackMetadata.

If AgentFeedbackMetadata gains new fields, this manual copy will silently drop them from the local handle state. Object.assign(feedbackHandle, updated) would be future-proof and shorter.

♻️ Suggested fix
      if (updated) {
-        feedbackHandle.traceId = updated.traceId;
-        feedbackHandle.key = updated.key;
-        feedbackHandle.url = updated.url;
-        feedbackHandle.tokenId = updated.tokenId;
-        feedbackHandle.expiresAt = updated.expiresAt;
-        feedbackHandle.feedbackConfig = updated.feedbackConfig;
-        feedbackHandle.provided = updated.provided;
-        feedbackHandle.providedAt = updated.providedAt;
-        feedbackHandle.feedbackId = updated.feedbackId;
+        Object.assign(feedbackHandle, updated);
      }
packages/core/src/agent/feedback.spec.ts (1)

15-41: Consider adding a case for feedbackId-only detection.

isFeedbackProvided has three independent marker checks (provided, providedAt, feedbackId), but only the first two are exercised here. Adding a case with only feedbackId set would close the coverage gap.

♻️ Suggested addition
     expect(
       isFeedbackProvided({
         traceId: "trace-1",
         key: "satisfaction",
         url: "https://example.com/fb",
         providedAt: "2026-02-12T00:00:00.000Z",
       }),
     ).toBe(true);
+
+    expect(
+      isFeedbackProvided({
+        traceId: "trace-1",
+        key: "satisfaction",
+        url: "https://example.com/fb",
+        feedbackId: "fb-1",
+      }),
+    ).toBe(true);
   });

Comment on lines +190 to +196
if (
messageTraceId === feedbackTraceId &&
messageKey === feedbackKey &&
messageUrl === feedbackUrl
) {
return message.id;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Empty-string fallback match can produce false positives.

When feedbackTraceId, feedbackKey, and feedbackUrl all resolve to "" (e.g., malformed input), the triple equality on Line 190-194 will match any message whose feedback fields are also empty or missing. This silently returns the wrong message ID.

Consider short-circuiting when all three fallback fields are empty:

♻️ Suggested guard
+    if (!feedbackTraceId && !feedbackKey && !feedbackUrl) {
+      return undefined;
+    }
+
     for (let index = messages.length - 1; index >= 0; index--) {
🤖 Prompt for AI Agents
In `@packages/core/src/agent/feedback.ts` around lines 190 - 196, The current
equality check using messageTraceId === feedbackTraceId && messageKey ===
feedbackKey && messageUrl === feedbackUrl can false-positive when all three
feedback fallbacks are empty strings; add a short-circuit guard before this
check that skips matching if feedbackTraceId, feedbackKey, and feedbackUrl are
all empty/blank (or falsy), e.g., require at least one non-empty feedback field
before returning message.id so you don't accidentally match messages with
missing/empty feedback fields.

@omeraplak omeraplak merged commit ec82442 into main Feb 13, 2026
23 checks passed
@omeraplak omeraplak deleted the feat/feedback-status-persist branch February 13, 2026 15:25
@coderabbitai coderabbitai bot mentioned this pull request Feb 17, 2026
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant