Files
deepseek-harness/packages/feedback/message-feedback

description, kind
description kind
Per-message ratings and notes for finalized assistant messages, for users and maintainers choosing, composing, or debugging the feedback service. package-reference

@deepseek-ai/dsh-message-feedback

English | 中文

Summary

dsh-message-feedback lets product surfaces offer per-message feedback: a user marks an assistant message positive or negative and can attach a short note, and the rating stays with that message. Ratings are stored with the session, survive restarts, and never enter model history or telemetry. Product surfaces read, create, and change ratings through the messageFeedback service, whose list, put, and delete operations are the whole surface. The one deployment setting is the maximum note length (maxNoteBytes), which the Web bundle sets to 8192. Browser controls live in a separate client package; this package provides the service itself.

Table of Contents


Use this package

Choose this service when a product surface should let users rate and annotate individual assistant messages. Feedback attaches only to a finalized message — one that has already been sent — and using the service never starts or resumes an agent. A custom app mounts the service together with session persistence and storage; the shipped Web bundle already composes all of it with maxNoteBytes: 8192.

Configuration

Field Default Meaning
maxNoteBytes required Maximum UTF-8 byte length accepted for one optional note.
- id: message-feedback
  name: '@deepseek-ai/dsh-message-feedback'
  config:
    maxNoteBytes: 8192

A note must contain at least one non-whitespace character and fit within the configured byte length; a blank note is rejected with note-blank and an oversized one with note-too-large. Accepted text is stored exactly as submitted — nothing is trimmed. The generated configuration catalog is the exhaustive source for every accepted field and its JSDoc.

Reading and changing feedback

Callers use three operations to read and change feedback for a session:

Operation Request Success Rejected when
list the session id the current ratings and notes, in creation order the session is not found
put session, message, rating, optional note, expected version the stored rating and note session not found, message is not a valid target, version conflict, blank or oversized note
delete session, message, expected version the rating is absent session not found, version conflict

Every change must be based on the version the service returned for that rating: a change based on an older version is rejected with version-conflict, and the reply carries the current rating so the caller can see what changed without another read. Deleting a rating that is already absent succeeds, and concurrent changes to different messages do not conflict. An omitted note clears an existing note.

What you can rate

A rating attaches to one finalized assistant message: the message must exist and be an assistant message that was sent. User messages, empty assistant placeholders, and replaced messages are not valid targets and are rejected with target-not-found. Once recorded, the rating and note stay with that message and survive restarts; a fork of the session starts with no feedback.

Durability

A rating is committed only after the message it refers to is durably stored, so feedback never points at a message that can be lost. Reading or writing feedback never starts or resumes an agent; the service inspects the persisted session directly.


Understand the implementation

Implementation internals — click to expand

Design concept

The service keeps feedback outside the session log entirely: each session owns one sidecar row in a storage domain, so a rating can never be confused with conversation content, model history, or telemetry. The sidecar is only ever committed after the message it references is durable — the row extends the target log instead of preceding it. Every operation returns a business result that distinguishes a handled failure (missing session, invalid target, stale version, bad note) from an infrastructure failure, which rejects instead of being mislabeled.

What a sidecar holds

One row per session binds the inspected session identity (createdAt, cwd) to its feedback items; the identity fences a reused session id, so a row from an earlier lifecycle is invisible and a fork starts with no feedback. Items are immutable values — a change writes a new version of the item, preserving its creation time — and the row schema rejects duplicate message ids and reused versions so lookup stays unambiguous. The exact row schema and validation live in src/spec.ts.

Concurrency

Mutations are optimistic and per message: a caller sends the version it last observed, a stale version is rejected with the authoritative current item so the caller reconciles without another read, and every material change mints a fresh version token so a stale write can never masquerade as current. A per-session queue serializes the whole read-compare-write through one service instance; storage provides no cross-process conditional write, which is the Known Limitation below.

Durability and target validation

A write is staged, verified, then committed: the target message is flushed through the canonical checkpoint, the physical log prefix is re-read, and only then is the sidecar row written — feedback can never reference a message that is not durable. Cold sessions are inspected without resuming an agent, absence is decided from the persistence catalog rather than guessed, and only a real, sent assistant message is a valid target. The flush and inspect path lives in src/index.ts.

Failure modes

The service fails closed: disposal drains in-flight writes before closing the domain, a write submitted after disposal starts is rejected as a lifecycle failure, and invalid configuration or a read before domain initialization fails loudly.

Source map

File Role
src/index.ts Service class: config validation, per-Session queue, durability barrier, @Remote methods
src/types.ts Public request, value, and failure vocabulary (types only, for generated Remote clients)
src/spec.ts Storage-domain declaration: message_feedback domain, sessions table, row schemas
src/invariant.ts Invariant companion (no runtime invariant; the domain schema validates rows on reopen)

Further Exploration

Read these pages when the package-level contract is not enough. They move from the subsystem types and design boundary to the persistence primitives and the browser consumer that drives this service.


Model Experience

Local message-feedback state

What the model sees

Nothing. ctx.messageFeedback registers no tool, prompt section, model-facing context, or Session event; feedback stays in a Host-owned sidecar unless a separately documented Consumer explicitly exposes it.

Token effect

Zero. No request, result, rating, note, timestamp, or failure from this package enters a model request.

KV Cache effect

Independent. Listing or mutating message feedback does not touch a model request prefix and cannot invalidate an otherwise reusable provider cache entry.

Known Limitations and Deferred Work

These limits define when the service is a poor fit or needs special operational care. They are current package constraints, not a task backlog.

  • Compare-and-set is single-process — the per-Session queue serializes one service instance only; storage-domain has no cross-process conditional write, so multiple Host processes writing one storage root can still lose updates.
  • No durable Session deletion cascade — Session persistence has no deletion API, and session/disposed/api-session/removed mean detach rather than durable deletion. The service therefore retains empty rows and may leave orphan rows after out-of-band log removal instead of deleting valid feedback on detach.
  • Detach/catalog retirement window — a request in the narrow interval after live detach but before the persistence catalog materializes the header can receive session-not-found; callers retry after retirement materialization.
  • Header identity is not a content fingerprint{createdAt, cwd} detects reuse only when those fields differ; a cloned log retaining the same header identity is indistinguishable.
  • Trusted caller boundarylist/put/delete carry no authenticated actor or audit identity. A deployment must expose the Host gateway only through its trusted or separately authenticated boundary until authorization and attribution are added.
  • Catalog and row bounds — a cold request scans the complete Session snapshot catalog because persistence has no lookup-by-id metadata operation. maxNoteBytes bounds one note, but the item count and aggregate retained bytes of one Session row are not capped; an indexed metadata read and deployment-owned row bound remain deferred until a concrete consumer defines their policy.

Dev Note

Working context for maintainers — click to expand

This Dev Note is working context for maintainers; it is explicitly non-authoritative. Shipped behavior, limits, and rationale live in the sections above, the package code, and the linked Agent Note.

  • The browser controls and the client Remote mount live in dsh-client-ui-message-feedback and dsh-api-remotes; their open items belong to those packages' notes.
  • The trusted-caller limitation is the open authorization direction: the Host gateway records no actor or audit identity, and any authentication layer must land at the deployment boundary before the service exposes per-user attribution.
  • Note validation precedes Session lookup by design, so note-blank and note-too-large win over session-not-found for a missing Session; tests pin this order.