# Instrument: browser, events & feedback

Feedback is telemetry: it lands next to the session it grades, and the same
loop that clusters failures clusters bad feedback — negative feedback becomes
bug candidates; directional asks become idea candidates whose text IS the spec.

Your `feedback_key` (`lk_feedback_*`) is **browser-safe** — it can submit
feedback and targeted events, plus read its own project's ingest health;
nothing else (no telemetry reads).

## Typed feedback

Target one of `session_id` / `trace_id` / `span_id`, plus at least one
valence (`score` 0..1, `polarity` up|down, `rating` 1..5, `comment`, or
`expected` — "what it should have said", a golden-answer seed):

```http
POST /v1/feedback
Authorization: Bearer <feedback_key>
Content-Type: application/json

{ "session_id": "sess_abc", "polarity": "down", "labels": ["export"],
  "comment": "the export button does nothing on mobile" }
```

## Spoken feedback

POST raw recording bytes (`audio/*`, ≤ 4 MiB); the worker transcribes at the
edge and the transcript enters the same lane. Raw audio is not retained:

```http
POST /v1/feedback/audio?session_id=sess_abc
Authorization: Bearer <feedback_key>
Content-Type: audio/webm

<recording bytes>
```

`@getloopops/sdk` ships `feedback()` / `feedbackAudio()` plus a drop-in
widget (`feedback-widget.js`: thumbs, comment panel, mic).

## Product / UX events

Standard OTLP logs with `EventName` — regenerations, retries, abandons,
escalations, slow-UX markers. Actionable friction becomes a bug candidate;
positive actions stay context:

```http
POST /v1/logs
Authorization: Bearer <feedback_key>
Content-Type: application/json

{ "resourceLogs": [{ "scopeLogs": [{ "logRecords": [{
  "traceId": "tr_123",
  "eventName": "chat.response_regenerated",
  "body": { "stringValue": "user regenerated the response" },
  "attributes": [
    { "key": "session.id", "value": { "stringValue": "sess_abc" } },
    { "key": "loopops.event.action", "value": { "stringValue": "regenerated" } }
  ]
}] }] }] }
```

With the `feedback_key`, events must be **targeted** (carry `session.id`,
`trace_id`, `span_id`, or a message id). Ordinary application logs use the
producer key.

## Evaluator results — the OTel standard just works

Automated evals instrumented to the OpenTelemetry GenAI semantic conventions
need no LoopOps-specific shape: a `gen_ai.evaluation.result` log event
(semconv v1.39) posted to `/v1/logs` is recognized at ingest and folds into
this same feedback lane as **agent** feedback. `gen_ai.evaluation.score.label`
(pass/fail-style verdicts) or `score.value` becomes the polarity,
`gen_ai.evaluation.explanation` the comment, and `gen_ai.response.id` the
join back to the evaluated call. The standard defines no human-feedback
event — human 👍/👎 stays on `/v1/feedback` above, where the rater split
(end user / reviewer / auto) already goes further than the spec.

**Next:** [Run your first loop →](/docs/run-your-first-loop)