Customer Support
AI agents that know when to escalate to a human
Resolve routine tickets autonomously, surface context instantly, and hand off to humans seamlessly โ with full conversation history.
The problem
Why this is hard today.
Chatbots that frustrate customers
Scripted chatbots can't handle nuance. Customers get stuck in loops, abandon the chat, and call the support line anyway.
Agents without guardrails
Autonomous agents that can't escalate are a liability. One wrong refund, one leaked detail, and trust is gone.
Context lost on handoff
When a bot hands off to a human, the conversation history disappears. The customer repeats everything. CSAT drops.
Architecture
How the three libraries compose.
Three libraries give your support agents superpowers โ and your customers a seamless experience.
Agent
@threadplane/langgraphLangGraph interrupts let the agent pause before sensitive actions โ refunds, account changes, escalations. Thread persistence preserves the full conversation across bot-to-human handoffs.
See Agent docs โRender
@threadplane/renderThe agent renders structured UI โ order summaries, refund confirmations, knowledge base cards โ instead of dumping text. Customers see clean, actionable information.
See Render docs โChat
@threadplane/chatProduction-ready chat UI with streaming messages, tool call visibility, and interrupt panels. When the agent escalates, the human agent sees the full debug overlay with every step the AI took.
See Chat docs โWhat you ship
Capabilities the framework delivers.
Full conversation history across bot-to-human handoff โ no repeating the question, no re-explaining the problem
Refunds and account changes resume only with an identified approver โ the agent cannot self-authorize
Tool-call replay for human agents on escalation โ see every step the AI took before the handoff
In practice
What it looks like in your codebase
support-chat.component.ts โ the escalation gate
export class SupportChatComponent {
protected readonly agent = injectAgent(SUPPORT_AGENT);
// Populated only while the graph is paused on an interrupt.
readonly pendingRefund = computed(() => this.agent.interrupt());
readonly awaitingHuman = computed(() => this.pendingRefund() !== null);
// Resuming carries the approver, so the record shows who authorized it โ
// the agent has no path to approve its own refund.
approveRefund(approver: string) {
this.agent.submit({ resume: { approved: true, approver } });
}
denyRefund(reason: string) {
this.agent.submit({ resume: { approved: false, reason } });
}
send(message: string) {
this.agent.submit({ message });
}
}support-chat.component.html
<chat [agent]="agent" />
@if (pendingRefund(); as pending) {
<aside class="approval">
<h3>{{ pending.value.action }} โ {{ pending.value.amount | currency }}</h3>
<p>{{ pending.value.reason }}</p>
<button (click)="approveRefund(currentAgentName())">Approve</button>
<button (click)="denyRefund('Outside policy')">Deny</button>
</aside>
}See it running
The approval gate, in the product
The agent proposes a destructive action, the graph pauses, and nothing runs until a human approves it.
Recorded from the live demo, which you can drive yourself.
Field report
The last-mile gap in Angular AI.
- Six production-readiness dimensions for Angular AI
- Concrete patterns โ error boundaries, fallbacks, observability, deploy
- No vendor pitch. Just what we learned shipping it.
Already on the list? Download the PDF directly.
Support agents that make your team better
Download the field report or start a pilot. Resolve routine tickets, escalate the rest with full context, keep your customers happy.