The Signal-to-Fix loop
Goal: shorten the time from the first affected conversation to every rep giving the right answer.
Show architecture ▾
Every enterprise runs on the same Signal-to-Fix Engine and the same data model. Each picks the tooling that matches how it works today, and can move up a tier without re-integrating.
Why each tier is faster
| Tier | What changes, and why it removes waiting | Effect |
|---|---|---|
| Human-led baseline |
|
~100 conversations without the right guidance |
| Mixed |
|
~70 |
| Agent-led |
|
Guidance ~4h after first report → ~3 |
Metrics — how I'd know it's working
North star metric — did customers get the right answer sooner? Economic buyer · VP CS / CX
| Metric | Direction | |
|---|---|---|
| North star | Avg conversations without the right guidance, per issue | ↓ |
| Guardrail | Poor-outcome rate after guidance goes live | ↓ must drop vs. before guidance |
Triage — spot the pattern, confirm the likely root cause, report it CX Lead
| Metric | Direction | |
|---|---|---|
| Primary metric | Avg conversations needed to report a product issue with confidence | ↓ |
| Guardrail | Reports rejected by product; conversations removed by the CX Lead | ↔ flat |
Investigate & inform — find the cause, get the right guidance to reps and customers Enterprise engineer
| Metric | Direction | |
|---|---|---|
| Primary metric | Developer satisfaction: how much effort the engineer says the investigation took, rated after each resolved issue | ↑ |
| Secondary metric | Avg time from report → investigation complete (root cause found or info requested) | ↓ |
| Guardrail | Guidance retraction rate (corrected or withdrawn within 24h) | ↔ flat |
Every speed metric is paired with a guardrail that catches it going wrong.
Quality signals
Frustration at startHow upset the customer is when they arrive, before support has done anything.low · medium · high
Why it matters: separates what the customer brought in from what support did. Without it, reps get blamed for customers who arrived angry, and incident severity is understated: high entry frustration means customers already hit the problem, maybe more than once.
Used for: incident severity; normalizing the trajectory signal.
Sentiment trajectoryHow the customer's sentiment changed from the start of the conversation to the end.improved · flat · worsened
Why it matters: the part of the experience support actually controls. A customer who arrives furious and leaves calm is a good conversation; a raw sentiment score would call it bad.
Used for: measuring whether guidance is working; a worsened trajectory feeds the "ended poorly" outcome.
Resolved in conversationWhether the customer's problem was solved before the conversation ended.yes · no · escalated
Why it matters: the outcome that drives cost (repeat contacts, escalations) and CSAT. During an incident it's the clearest sign reps don't have the right answer.
Used for: the "ended poorly" outcome on the impact chart.
Likely root causeThe most probable reason the customer contacted support, with a confidence score.product issue · policy · knowledge gap · external
Why it matters: different causes need different owners. Only product issues go to the product team; knowledge gaps go to support training; external causes (theft, carrier) go nowhere. Routing everything everywhere is how teams learn to ignore alerts.
Used for: deciding whether a cluster is reported to the product team.
Cluster fitHow closely a conversation matches the rest of its cluster.0–1
Why it matters: lets the CX Lead review by exception instead of reading every conversation. Low-fit conversations (e.g., porch theft inside a "delivered but not received" cluster) are flagged as unsure. The CX Lead's removals and additions become labeled data for cluster precision and recall.
Used for: human review of the cluster before it's reported.
Deliberately not included · how I'd validate the signals
- Empathy / agent tone scores — useful for rep coaching, not incident detection. Different loop, different buyer.
- CSAT prediction — would duplicate the three experience signals without adding an action.
- Agreement with human QA labels on a sample of ~200 conversations, per signal.
- CX Lead removals and additions → precision and recall of clusters over time.
- Resolved-in-conversation vs. actual repeat contacts within 7 days.
Integration specs
| Interface | Shaped around | Consumer |
|---|---|---|
| Conversation dashboard | Views — screens over the same objects | People (CX Lead, engineers) |
| Core API | Resources — one endpoint per object | Code (including the dashboard) |
| Webhook | Events — "something happened to an Issue" | Systems |
| MCP | Intents — "help me do this task" | A model working with a human |
| A2A | Tasks — "own this outcome" | Another agent |
Core data model
| Object | Key attributes |
|---|---|
| Case | case_id, started_at, channel, signals: frustration_at_start, trajectory, resolved, likely_root_cause {type, confidence}; issues: [{issue_id, cluster_fit, added_by}] |
| Issue | issue_id, title, summary, likely_root_cause {type, confidence}, case_count, first_seen, state |
| Evidence | issue_id, case_id, turn, quote, request_id (set when the quote answers an InfoRequest) |
| InfoRequest | request_id, issue_id, question, answers_needed, answers {count, breakdown}, each answer linked to Evidence |
| Guidance | guidance_id, issue_id, audience (rep / customer), text, approval (auto / pending / approved), status (draft / live / retired) |
Issue lifecycle: reported → investigating → needs_info → resolved
Relationships: a Case can belong to more than one Issue, so membership lives on the Case, with cluster_fit relative to that Issue and added_by (system / cx_lead) recording the CX Lead's review. Evidence always points to the Issue it supports, and to the InfoRequest when it's an answer.
{
"issue_id": "ISS-1042",
"title": "Delivered but not received",
"likely_root_cause": { "type": "product_issue", "confidence": 0.91 },
"case_count": 37,
"first_seen": "Mon 12:48",
"state": "needs_info"
}
Ingestion API
Batch in, scored cases out. This is where every Case comes from.
POST /v1/conversations:batch
// request
{ "conversations": [ { "conversation_id": "C-40811", "started_at": "Mon 13:12",
"channel": "chat",
"turns": [ { "speaker": "customer", "text": "Tracking says delivered but nothing at my door." } ] } ] }
// response
{ "scored": 212,
"cases": [ { "case_id": "C-40811",
"signals": { "frustration_at_start": "high", "trajectory": "worsened", "resolved": "no",
"likely_root_cause": { "type": "product_issue", "confidence": 0.94 } } } ] }
Core API
Resource-shaped: one endpoint per object, for code and for the support platform's own dashboard. Every other interface is built on it.
| Endpoint | Purpose |
|---|---|
GET /v1/issues · GET /v1/issues/{id} | List and read Issues |
GET /v1/issues/{id}/cases | Cases in an Issue, with signals |
GET /v1/cases/{id}/evidence | Evidence for a Case |
POST /v1/issues/{id}/info-requests · GET /v1/info-requests/{id} | Create and read InfoRequests |
POST /v1/guidance · PATCH /v1/guidance/{id} | Draft, approve, retire Guidance |
PATCH /v1/issues/{id} | Update Issue state |
Webhook
Event-shaped: pushes an Issue into the enterprise's tracker the moment the CX Lead reports it. The payload carries enough to triage without logging into the support platform.
issue.reported
{
"event": "issue.reported",
"event_id": "evt_8f2c",
"issue": { "issue_id": "ISS-1042", "title": "Delivered but not received",
"likely_root_cause": { "type": "product_issue", "confidence": 0.91 },
"case_count": 37, "first_seen": "Mon 12:48" },
"evidence": [ { "issue_id": "ISS-1042", "case_id": "C-40811", "turn": 1,
"quote": "Tracking says delivered but nothing at my door." } ],
"investigate_via": { "mcp_server": "signal-mcp", "issue_id": "ISS-1042" }
}
Signed (HMAC); event_id makes delivery idempotent.
MCP server signal-mcp
Intents → tools
| Engineer's intent | Tool | Replaces (Core API) | Returns |
|---|---|---|---|
| "What's going on?" | investigate_issue(issue_id) | GET /issues/{id} + GET /issues/{id}/cases + aggregation | Summary, trend, likely root cause, what cases have in common (and don't), representative evidence |
| "Show me cases that mention X" | find_evidence(issue_id, query) | GET /cases?filter=… + reading transcripts | Matching quotes with case and turn references |
| "Ask affected customers something" | ask_customers(issue_id, question, answers_needed) | POST /info-requests + POST /guidance + approval | Request status and what happens next |
| "What did they say?" | get_customer_answers(issue_id) | GET /info-requests/{id} + answers | Breakdown, whether the target was reached, sample answers linked to evidence |
| "It's fixed" | mark_resolved(issue_id, root_cause, customer_impact) | PATCH /issues/{id} + POST /guidance | Confirmation and what happens next |
Deliberate escape hatch: intent-shaped tools are opinionated. find_evidence stays flexible for questions nobody anticipated.
One tool in full: ask_customers
{
"name": "ask_customers",
"description": "Ask customers affected by an issue a diagnostic question. Use when the cause
can't be found from existing conversations and customers likely know something your
systems don't (their setup, what they saw, what they did). The support platform drafts rep guidance so
reps ask the question in new conversations; answers are extracted automatically.
Never ask for payment details, passwords, or other sensitive data.",
"input": {
"issue_id": "string",
"question": "string — one question a rep can ask in plain language",
"answers_needed": "integer — answers to collect before notifying (default 20)"
}
}
// response
{
"request_id": "RQ-77",
"issue_state": "needs_info",
"next": "Rep guidance drafted; reps start asking once the CX Lead approves.
Answers will be available via get_customer_answers."
}
A2A
Task-shaped: the support platform's agent hands outcomes to the enterprise agent that owns each system. Follows A2A concepts (Agent Card, Task, task states); field names abbreviated.
Agent cards — skills are intent-shaped, like the MCP tools
| Agent | Owner | Skills |
|---|---|---|
| Signal Agent | Support Platform | find_evidence, ask_customers |
| Release Agent | Enterprise | correlate_changes |
| Engineering Agent | Enterprise | investigate_issue |
| KB Agent | Enterprise | publish_guidance, retire_guidance |
{
"name": "Signal Agent",
"description": "Surfaces product issues from customer conversations and collects
answers from affected customers.",
"capabilities": { "streaming": true, "pushNotifications": true },
"skills": [
{ "id": "find_evidence", "description": "Find customer quotes about an issue that match a question." },
{ "id": "ask_customers", "description": "Collect answers to a diagnostic question from affected customers." }
]
}
The Issue lifecycle maps onto A2A task states
the support platform sends investigate_issue to the Engineering Agent. The task's state is the Issue's state:
| A2A task state | Issue state | What's happening |
|---|---|---|
submitted | reported | The support platform hands over the Issue with evidence and correlated changes |
working | investigating | Engineering Agent routes to the owning engineer |
input-required | needs_info | Engineering Agent needs customer answers; The support platform asks customers and replies on the task |
completed | resolved | Fix verified; resolution returned as a task artifact |
// Engineering Agent → Signal Agent: task needs input
{
"task_id": "T-311",
"skill": "investigate_issue",
"status": { "state": "input-required",
"message": "Can't reproduce. Ask affected customers: 'Do you live in an apartment or
building with a mailroom or parcel locker?' Need 20 answers." },
"context": { "issue_id": "ISS-1042" }
}
The same holds for the other agents: correlate_changes completes with the matching changes as an artifact; publish_guidance completes when guidance is live — immediately for rep-facing guidance, after CX Lead approval for customer-facing.
Tradeoffs
What I'd change for production
- Version the data model and every interface. Issue and Case carry a schema version, and the Core API, webhook events, and MCP tools are versioned separately. Enterprises can then move up a tier, or stay put, without anything breaking underneath them.
- Offline evals for classification and clustering. Build a labeled set of conversations, and measure each signal's agreement with human labels plus cluster precision and recall. No model or prompt change ships unless it holds or improves those numbers.
What I'd measure
- The metrics in the Metrics tab: the north star and each stage's primary metric, each with its guardrail.
- Tier adoption and migration friction: how many enterprises are on each tier, and what it costs to move up (time to onboard, integration issues along the way). If moving up is hard, "no re-integration" isn't true yet.
- MCP usage: which tools engineers call, how often, and whether a session ends in an action (
ask_customers,mark_resolved). Tools nobody calls mean I guessed the wrong intents.
What I'm unsure about
- Webhooks. I don't know yet whether product teams want issues pushed into their tracker, or a different channel such as Slack, on-call paging, or a digest. I'd validate with a handful of product teams before building more than one channel.
- A2A. I don't know whether enterprises will run their own release, engineering, and KB agents, or whether A2A is the protocol they'll adopt. I'd build it with one or two design partners before generalizing.