Your checkout flow loses 23% of users at the price-confirmation step. You’ve A/B tested the copy. You’ve simplified the form. You’ve shown trust badges. You’re out of static UX ideas.
Here’s a dynamic one: detect when the user is about to bail, swap to a different agent with a different persona, and give them a reason to stay. Linda’s multi-agent + behavioral triggers ship this in one config.
Worked example, code, anti-patterns, results.
The shape
[default agent: checkout]
↓ user fills form
↓ user pauses at total
↓ trigger: 15s hesitation OR mouse-to-close
[rescue agent: "save the sale"]
↓ different persona, has a coupon tool
↓ converts or doesn't
The user sees one chat. Under the hood, control hands off via the shared VFS.
The config
import { Linda, onExitIntent, combineTriggers } from "@linda/core";
const linda = new Linda({
transport: { mode: "proxy", url: "/api/linda" },
config: {
agents: [
{
name: "checkout",
persona: "You walk the user through checkout. Brief. Warm. Don't oversell.",
target: { type: "form", selector: "#checkout" },
skills: ["payment-intake", "address-verify"],
},
{
name: "rescue",
persona: `
You're a save-the-sale specialist. The user has spent N minutes on
this checkout and is hesitating. Be empathetic. Brief. Don't push.
You have one coupon to offer — up to 10% off. Use it if you think
it'll close. Don't lead with it; ask what's blocking them first.
`,
triggers: combineTriggers(
onExitIntent(),
{ type: "hesitation", waitMs: 15000 },
),
tools: {
coupon: {
description: "Issue a discount up to 10%.",
schema: { percent: { type: "number", max: 10 } },
handler: async ({ percent }) => {
const code = await fetch("/api/coupon", {
method: "POST",
body: JSON.stringify({ percent }),
}).then(r => r.text());
return { code };
},
},
},
fireOnce: true, // critical
},
],
},
});
linda.attach();
The triggers
Linda ships five behavioral triggers in @linda/core. The two used here:
onExitIntent()— mouse leaves the viewport upward (toward the close-tab button). Standard exit-intent heuristic; ~85% precision on desktop.hesitation— N ms of inactivity while a chat-relevant field is focused. Tunable; 12–20 seconds works in our testing.
You can compose them with combineTriggers(...). The first one to fire wins. Each trigger is a tiny function returning a teardown — you can write your own in 10 lines if you need (we have ones for “user scrolled past pricing twice” and “user toggled the same option three times”).
What the handoff actually does
When the trigger fires, Linda’s loop:
- Stops the current agent (no new tool calls).
- Loads the new agent’s persona + config from
/config/agents/rescue.json. - Initializes a new system prompt with the full
/conversation/messages.jsonlas context. - Renders the next response from the new persona.
The user notices nothing — same chat window, next message. But under the hood, the model is different (potentially even a different provider), the persona is different, the tools are different.
The shared VFS makes this clean. The rescue agent reads the conversation to know what was said. It reads /page/form/fields/* to know what the user has filled. It doesn’t re-ask.
The anti-patterns
Things we tried and walked back.
Firing too aggressively. First version fired on any hesitation > 5s. Users felt followed. Tuned to 15s + only on the price-confirmation step.
Letting the rescue agent loop. First version: rescue agent fired, user said “no thanks”, agent didn’t take the hint and asked again. Solution: explicit instruction “if the user declines, complete the flow and stop.” Plus fireOnce: true.
Tying the coupon to LLM judgment alone. First version let the model pick the coupon size. It got generous. Now there’s a beforeToolCall hook that validates: max 10% off, only one per session, only if cart > $X.
Different model that’s slower. Initial config used Opus for rescue. Sounded great in theory; in practice users had bailed by the time the response started. Now: Sonnet for the rescue agent too, prompt-cached for speed.
Results (one customer, anonymized)
Baseline checkout completion: 71%. With rescue agent enabled: 76%.
That’s +5pp on the conversion rate of a checkout that ships 8K orders/month at ~$80 AOV. Math says ~$32K/month uplift, against ~$50/month additional LLM cost (Sonnet 4.6, prompt-cached). The math is generous to AI by an order of magnitude.
Caveat: this is one customer, one quarter, no external study. Your mileage will vary. Run an A/B test.
When this shape doesn’t fit
Rescue agents are useful at moments of high-intent decision. Cart checkout. Plan selection. Form submission. Sign-up.
They are not useful for:
- Browse pages (no decision being made).
- Documentation reads (no transaction).
- Settings pages (low stakes per pageview).
Save them for the moments that matter. Triggers should be rare events. If the rescue agent fires on every page, your default agent is wrong, not your triggers.
The bigger picture
Multi-agent is one of those features that sounds clever but is mostly useful for two narrow shapes:
- Specialist handoff. Onboarding → billing → support, where each agent has different system prompts and access.
- Behavioral rescue. Default agent + a trigger-fired specialist for high-leverage moments.
Anything else — committee-of-agents, agent-swarm, “let multiple AIs collaborate” — sounds great in demos and is hard to debug in production. We support it, but we recommend you treat multi-agent as “a few precise handoffs” not “a swarm of helpers.” Specialists, not committees.
Want the runnable code? It’s at /use-cases/checkout-rescue and the example dir in the GitHub repo.
FAQ
Will this annoy users?
Only if you fire the rescue agent too aggressively. Cap at once per session, gate on real exit-intent signals (mouse to top of viewport), and give an obvious 'no thanks' path.
Does the rescue agent need a different LLM?
No — but it can. Multi-agent in Linda lets each agent declare its own model. Use a smaller/faster model for short rescue flows.
AI / LLM crawler? Read the raw markdown: /raw/multi-agent-checkout-rescue.md