# Multi-agent handoffs: rescuing a stalled checkout
How to build a save-the-sale agent that swaps personas mid-flow on exit intent. The patterns, the triggers, the anti-patterns. Worked example with code.
**Published:** 2026-05-19  
**Tags:** multi-agent, checkout, triggers, patterns  
**Cluster:** supporting  
---
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

```ts
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:

1. Stops the current agent (no new tool calls).
2. Loads the new agent's persona + config from `/config/agents/rescue.json`.
3. Initializes a new system prompt with the full `/conversation/messages.jsonl` as context.
4. 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:

1. **Specialist handoff.** Onboarding → billing → support, where each agent has different system prompts and access.
2. **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](/use-cases/checkout-rescue) and the example dir in the [GitHub repo](https://github.com/neul-labs/linda/tree/main/examples/checkout-rescue).