# How we turn feedback into a roadmap

On a schedule, an agent gathers feedback from support, public reviews, and a Slack channel, clusters it into themes with representative quotes and counts, and creates or updates a Linear issue per theme.

Canonical page: https://kortix.com/use-cases/user-feedback

Product feedback arrives everywhere: support threads, public reviews, a Slack
channel where people drop what they hear. The same request shows up in all three,
worded differently each time, and never gets counted. A theme that a hundred
people asked for looks the same as a one-off, because nothing pulls the mentions
together.

We run a feedback agent on Kortix that gathers those sources on a schedule,
clusters them into themes, and keeps a Linear issue per theme up to date. It reads
the feedback and writes to Linear; people still own prioritization. This is how we
keep our own roadmap grounded in what users actually ask for.

- **Team:** Kortix
- **Runs on:** Scheduled cron
- **Connected systems:** Plain · G2 / app stores · Slack · Linear
- **Mode:** Reads feedback · writes Linear issues

## The problem

Feedback is scattered across support threads in Plain, public reviews on G2 and
the app stores, and an internal Slack feedback channel. The same underlying
request appears in all of them, phrased differently, so it never gets counted. A
theme that keeps recurring is indistinguishable from a single loud comment.

The common approaches don't add up to a roadmap. Reading each source by hand is
slow and inconsistent, and whoever reads it weighs it differently. A tag in the
support tool captures support but not reviews or Slack. A spreadsheet of feature
requests goes stale the moment someone stops maintaining it, and it still doesn't
tell you how many people asked for the same thing.

## What we built

On Kortix, a scheduled cron triggers an agent. It spawns an isolated session (a
cloud sandbox) with read access to Plain, the public review sources, and the Slack
feedback channel, and write access to Linear. It gathers the feedback, clusters it
into themes with representative quotes and counts, and creates or updates a Linear
issue per theme, so the same request is deduplicated and quantified instead of
scattered.

## How it works

### Run on a schedule

A **cron trigger** fires the agent on a schedule. Each firing spawns a fresh
**session** in its own sandbox. One run pulls the current feedback, reconciles it
against the existing themes in Linear, and updates them. Nothing carries over
between runs except what's written to Linear.

### Give the agent the clustering rules

How we group feedback lives as **skills** and **memory** that travel with the
agent: what makes two differently-worded requests the same theme, how to pick a
representative quote, how to title an issue, and how to match new feedback to an
existing theme instead of creating a duplicate. As our themes evolve, we write it
down and the clustering stays consistent.

### Connect the sources and Linear

Through scoped **connectors**, brokered server-side so no raw token reaches the
model, the agent can:

- **Read support from Plain** — recent threads, for the requests and complaints
users raise directly.
- **Read public reviews** — G2 and the app stores, for what users say in the open.
- **Read the Slack feedback channel** — the internal channel where the team drops
what they hear.
- **Write to Linear** — create a new issue for a new theme, or update the count and
quotes on an existing one.

### Set the guardrails

The agent is **read-only** on every source and its only write is to Linear, where
it creates and updates issues. It does not set priority, assign owners, or close
issues — those stay with people. Credentials are encrypted in the Secrets Manager
and injected at runtime, scoped to the agents you grant them to.

### Keep one issue per theme

With that in place, each run gathers the latest feedback, clusters it, and keeps
one Linear issue per theme current: representative quotes, a running count, and
the sources it came from. The same request stops being scattered across three
systems and becomes one quantified issue the team can weigh against the rest.

> **The pattern**
> A scheduled **cron** spawns a session with read **connectors** into Plain, the
> public reviews, and Slack, and a write connector into Linear. The clustering
> lives as **skills** and **memory**. The agent quantifies the themes; people own
> what to build.

## Guardrails

The agent reads several feedback sources and writes to Linear, so its access is
scoped to exactly that:

- **Isolation.** Every run happens in its own isolated sandbox. The session is granted access only to the sources it's scoped to, and only the Linear writes are written back out.
- **Scoped secrets.** The Plain, review-source, Slack, and Linear credentials are
  encrypted in the Secrets Manager and injected into the sandbox at runtime, scoped to the agents you grant them to.
- **Read-only sources, scoped writes.** The feedback sources are read-only; the
  only write is creating and updating Linear issues. The agent does not prioritize,
  assign, or close.
- **Everything is code.** The agent's clustering rules, skills, and per-system
  permissions are files in the repo, versioned and changed through a reviewed
  **change request** rather than a dashboard setting.

## The outcome

- **Every run:** Feedback gathered, clustered, and reconciled to Linear
- **One per theme:** The same request deduplicated instead of scattered
- **4 sources:** Support, reviews, and Slack into one issue tracker

Feedback that used to sit unread across support, reviews, and Slack now arrives as
a set of quantified themes, each a single Linear issue with quotes and a count.
The agent does the gathering and counting; the team still owns which themes become
roadmap.
