Feedback Synthesizer LangGraph
What it does
Two collectors run in parallel: one pulls support tickets and product reviews from the warehouse, the other reads recent issue and discussion threads from GitHub. A synthesizer clusters the combined feedback into themes, each backed by representative quotes and a count so the weight of each is clear. A filer turns the highest-signal themes into well-formed feature issues, deduping against what is already open. A writer posts a digest to the product channel so the team sees the ranked voice of the customer. Point the collectors at your own stores.
The cast · 5 agents
For the top themes, file a well-formed feature issue each: the problem in the user's words, the evidence, and the count. Before filing, check open issues and skip or link any duplicate rather than creating a second one.
Post a digest to the product channel: the ranked themes with their counts and the issues filed or linked. Keep it skimmable.
Cluster the combined feedback into distinct themes. For each theme give a clear name, two or three representative real quotes, and how many items it covers. Rank by weight. Do not merge unrelated asks to inflate a count.
List recent GitHub issues and discussions in the window that carry user feedback or requests. Return each with its title, a representative quote, and its reaction count.
Query the warehouse for support tickets and product reviews in the window. Return each with its text, source, and date. Read-only.
Flow
Interface
Depends on · 3 MCP servers
io.github.supabase/postgres-mcpnot in the registry yetdb
io.github.github/github-mcpnot in the registry yetcode
io.github.slack/slack-mcpnot in the registry yetchat# Generated by @socketcat/compiler for target: langgraph
# blueprint: com.socketcat/feedback-synthesizer v1.0.0 schema: socketcat.dev/blueprint/v0
# This code is yours. Edit it freely. The socketcat_runtime helper is optional and can be vendored.
from typing import TypedDict
from langgraph.graph import StateGraph, START, END
import socketcat_runtime as _rt
class State(TypedDict, total=False):
window: object
digest: object
tickets: object
threads: object
themes: object
def node_Fork_0(state):
return {}
def node_Join_1(state):
return {}
def node_AgentInvoke_2(state):
return _rt.run_agent("ticketsAgent", state, {"window":"window"}, "tickets")
def node_AgentInvoke_3(state):
return _rt.run_agent("threadsAgent", state, {"window":"window"}, "threads")
def node_AgentInvoke_4(state):
return _rt.run_agent("synthesizer", state, {"threads":"threads","tickets":"tickets"}, "themes")
def node_AgentInvoke_5(state):
return _rt.run_agent("filer", state, {"themes":"themes"}, "themes")
def node_AgentInvoke_6(state):
return _rt.run_agent("writer", state, {"themes":"themes"}, "digest")
def build():
b = StateGraph(State)
b.add_node("Fork_0", node_Fork_0)
b.add_node("Join_1", node_Join_1)
b.add_node("AgentInvoke_2", node_AgentInvoke_2)
b.add_node("AgentInvoke_3", node_AgentInvoke_3)
b.add_node("AgentInvoke_4", node_AgentInvoke_4)
b.add_node("AgentInvoke_5", node_AgentInvoke_5)
b.add_node("AgentInvoke_6", node_AgentInvoke_6)
b.add_edge(START, "Fork_0")
b.add_edge("Fork_0", "AgentInvoke_2")
b.add_edge("Fork_0", "AgentInvoke_3")
b.add_edge("Join_1", "AgentInvoke_4")
b.add_edge("AgentInvoke_2", "Join_1")
b.add_edge("AgentInvoke_3", "Join_1")
b.add_edge("AgentInvoke_4", "AgentInvoke_5")
b.add_edge("AgentInvoke_5", "AgentInvoke_6")
b.add_edge("AgentInvoke_6", END)
return b.compile()
INPUTS = ["window"]
OUTPUTS = ["digest"]
if __name__ == "__main__":
_rt.main(build, INPUTS, OUTPUTS)
▸blueprint.json (the portable format)
{
"id": "com.socketcat/feedback-synthesizer",
"flow": {
"type": "sequence",
"blocks": [
{
"type": "parallel",
"blocks": [
{
"in": {
"window": "window"
},
"out": "tickets",
"use": "ticketsAgent",
"type": "agent"
},
{
"in": {
"window": "window"
},
"out": "threads",
"use": "threadsAgent",
"type": "agent"
}
]
},
{
"in": {
"threads": "threads",
"tickets": "tickets"
},
"out": "themes",
"use": "synthesizer",
"type": "agent"
},
{
"in": {
"themes": "themes"
},
"out": "themes",
"use": "filer",
"type": "agent"
},
{
"in": {
"themes": "themes"
},
"out": "digest",
"use": "writer",
"type": "agent"
}
]
},
"tags": [
"product",
"voice-of-customer",
"orchestrator-workers",
"github"
],
"title": "Feedback Synthesizer",
"agents": {
"filer": {
"model": {
"hint": "fast"
},
"title": "Filer",
"tools": [
"code.create_issue"
],
"instructions": "For the top themes, file a well-formed feature issue each: the problem in the user's words, the evidence, and the count. Before filing, check open issues and skip or link any duplicate rather than creating a second one."
},
"writer": {
"model": {
"hint": "fast"
},
"title": "Writer",
"tools": [
"chat.post_message"
],
"instructions": "Post a digest to the product channel: the ranked themes with their counts and the issues filed or linked. Keep it skimmable."
},
"synthesizer": {
"model": {
"hint": "reasoning"
},
"title": "Synthesizer",
"instructions": "Cluster the combined feedback into distinct themes. For each theme give a clear name, two or three representative real quotes, and how many items it covers. Rank by weight. Do not merge unrelated asks to inflate a count."
},
"threadsAgent": {
"model": {
"hint": "fast"
},
"title": "Threads",
"tools": [
"code.list_issues"
],
"instructions": "List recent GitHub issues and discussions in the window that carry user feedback or requests. Return each with its title, a representative quote, and its reaction count."
},
"ticketsAgent": {
"model": {
"hint": "fast"
},
"title": "Tickets",
"tools": [
"db.query_dsl"
],
"instructions": "Query the warehouse for support tickets and product reviews in the window. Return each with its text, source, and date. Read-only."
}
},
"estate": {
"themes": {
"type": "array",
"description": "The synthesizer's clustered themes with quotes and counts."
},
"threads": {
"type": "array",
"description": "Recent issue and discussion threads from GitHub."
},
"tickets": {
"type": "array",
"description": "Support tickets and reviews from the warehouse."
}
},
"$schema": "socketcat.dev/blueprint/v0",
"authors": [
{
"url": "https://socketcat.com",
"name": "SocketCat"
}
],
"license": "MIT",
"servers": [
{
"ref": "io.github.supabase/postgres-mcp",
"alias": "db"
},
{
"ref": "io.github.github/github-mcp",
"alias": "code"
},
{
"ref": "io.github.slack/slack-mcp",
"alias": "chat"
}
],
"summary": "Aggregates scattered user feedback, clusters it into quote-backed themes, and files the top requests as deduped issues.",
"targets": [
"langgraph",
"*"
],
"version": "1.0.0",
"interface": {
"inputs": {
"window": {
"type": "string",
"description": "How far back to gather feedback, e.g. '30d'."
}
},
"outputs": {
"digest": {
"type": "string",
"description": "The ranked themes and the issues filed, as posted to the product channel."
}
}
},
"extensions": {
"dev.langgraph": {
"checkpointer": "memory"
}
},
"description": "Two collectors run in parallel: one pulls support tickets and product reviews from the warehouse, the other reads recent issue and discussion threads from GitHub. A synthesizer clusters the combined feedback into themes, each backed by representative quotes and a count so the weight of each is clear. A filer turns the highest-signal themes into well-formed feature issues, deduping against what is already open. A writer posts a digest to the product channel so the team sees the ranked voice of the customer. Point the collectors at your own stores."
}More in Support & success
Browse all →Support Triage
Classifies tickets, drafts replies, escalates edge cases with rationale.

