Concepts
Six primitives. The rest of Earshot is built on top.
Team
The billing + access boundary. A team has many members (admins or members) and many projects. Created during signup; usually one team per company.
Project
One widget install. Usually one production app. A project has:
- An id like
prj_q24wfw27mlh6— shows up everywhere. - A public API key like
pk_xxxxxxxxxxxx— embedded in the widget snippet. Rotate any time from the project settings page. - Optional integrations: at most one tracker (Linear or Jira) and any number of federation providers (PostHog, Sentry, LogRocket, Datadog).
- A project brief the enrichment LLM loads on every submission.
Feedback
The unit of work. One feedback row carries:
- A voice transcript (transcribed via Whisper) and the audio file.
- An annotated screenshot of the host page at capture time.
- A captured DOM snapshot (rrweb full-snapshot), recent console entries, recent network requests, browser/device/viewport/URL.
- An LLM-derived structured record: title, verbose summary, priority, labels — grounded in the project brief + captured browser context.
- Federation context per provider (replay URL, recent events, feature flags).
- Identity (if
identify()was called) — ornullfor anonymous submissions, with deferred-identity backfill. - Status (
triage/backlog/in_progress/done/canceled), priority (0 = highest, 4 = lowest), labels. - An
external_refpointing to the linked tracker issue (Linear url, Jira key). - Comments (with author type
human/agent/submitter).
Session
A short-lived row keyed by (project_id, session_id) used as the async progress tracker between the widget POST and the Inngest worker. Lifecycle:
processing— POST has uploaded the media + queued the worker.done— worker has transcribed + enriched + ingested. Carries the resultingfeedback_id.error— anything failed; carries a human-readable message.
The row TTLs out after one hour. The widget treats the POST 202 as "filed" and doesn't normally wait, but a client that wants progress can poll GET /v1/feedback/session/<id>/state.
Federation provider
A read-side integration (PostHog / Sentry / LogRocket / Datadog). On every feedback submission Earshot calls each connected federation provider to pull session context for the affected URL — replay URL, recent events, feature flag values. The enrichment LLM sees this; the eventual ticket carries it.
You bring your own keys. Nothing about your existing observability changes.
Tracker
A write-side integration (Linear or Jira). On ingest, Earshot files the structured report as an issue in your tracker with full context attached. Two-way sync mirrors status changes and comments back to the issue.
How they fit
End user clicks launcher
↓
Widget captures: voice → audio + transcript, screenshot, DOM, console, network, identity, federation
↓
POST /v1/feedback → 202 "processing"
↓
Inngest worker: transcribe (Groq) → enrichment LLM grounded in project brief + context → ingest
↓
Feedback row persisted, federation pulled, tracker issue filed
↓
Inbox shows the row; Linear shows the ticket; submitter gets an email on resolve
The MCP exposes every step as a tool. An agent doing triage reads the same data from get_feedback that the dashboard renders.