ChanlChanl
Agent Architecture

When voice becomes chat: carrying context across channels

When a customer switches from a call to chat, your AI agent loses everything it learned on the phone -- unless you package and transfer context deliberately. Here's how to build channel context handoffs that actually work.

DGDean GroverCo-founderFollow
May 31, 2026
14 min read
Diagram showing a voice conversation on the left connecting via a context package to a chat session on the right, with a unified customer profile in the middle

The customer had been on the phone for twelve minutes. She had confirmed her account, explained the overcharge on her last bill, and agreed to a credit rather than a refund. The agent asked if she wanted a follow-up confirmation by text. She said yes. The agent said the message would arrive in a few minutes.

The SMS arrived with a link to continue in chat if she had further questions. She clicked it an hour later to check the credit status. The chat agent opened with: "Hi there! How can I help you today?"

She had to explain everything from the start. She wrote a review the next day.

This is the channel context cliff. Your agent handles the voice call well. It handles the chat session well. What it doesn't handle is the transition between them, because transitions require architecture that most teams haven't built.

The invisible context cliff between channels

When a customer switches channels, your AI agent loses everything it learned in the previous session unless you explicitly package and transfer that context. Voice session state lives in your voice orchestration platform. Chat session state lives in your chat platform. Neither knows what happened in the other.

This isn't a model limitation -- it's an architecture gap. The model itself has no persistence across sessions. By design, each new session starts with whatever you put in its context window. If you don't inject the previous session's context, the agent has nothing to work from.

The customer experience consequence is real. Research from 2026 contact center operations studies found 48% of customers would abandon a brand after having to re-explain an issue following a channel switch, and 40% would leave if forced to re-verify their identity. The numbers are high because the experience is so frustrating -- customers have already invested time and emotional energy in a conversation, and having to start over feels like the system doesn't value that.

The fix is not complicated, but it has to be deliberate. At the end of any session that might continue in another channel, you package the relevant context, store it with a handoff token, and restore it when the customer arrives in the new channel.

What belongs in a channel context package

A context package is not a transcript. Dumping the full conversation into a JSON blob and hoping the next session's agent can make sense of it doesn't work well in practice -- the context window cost is high, and unstructured transcripts are hard to query.

What you want is a structured extraction of the semantically important elements. Think about what a very good human agent would write in a case note when handing off to a colleague. That's your target.

The elements that belong in a channel context package:

Intent. What the customer was trying to accomplish. A single sentence, specific enough that the next agent can act on it. "Customer is requesting a credit for $43.71 overcharge on May invoice, account ending 4821."

Progress state. What steps were completed and what's still pending. This prevents the next agent from re-asking for information that was already provided or re-confirming things that were already agreed.

Extracted entities. Account numbers, order IDs, product names, dates, and any other specific references that came up in the conversation. These become established facts the next agent doesn't need to re-establish.

Promised actions. Anything the previous agent committed to that hasn't happened yet. The customer is going to ask about these. The next agent needs to know they exist.

Sentiment snapshot. How the customer was feeling at the end of the session. A customer who ended a voice call frustrated and skeptical needs a different opening from one who ended satisfied and curious about a follow-up.

What doesn't belong: filler turns, pleasantries, clarifying questions that got answered, and anything resolved in the previous session.

context-package.ts·typescript
import Chanl from '@chanl/sdk';
 
const chanl = new Chanl({ apiKey: process.env.CHANL_API_KEY });
 
// After a voice session ends, extract and store the context package
async function packageChannelContext(
  sessionId: string,
  customerId: string,
  destinationChannels: string[]
) {
  const session = await chanl.calls.getSession(sessionId);
 
  const extraction = await chanl.memory.extract({
    customerId,
    transcript: session.transcript,
    extractTypes: [
      'customer_intent',      // What they were trying to accomplish
      'progress_state',       // Which steps completed, which pending
      'pending_actions',      // Promises made, not yet fulfilled
      'extracted_entities',   // Account IDs, order numbers, dates, products
      'sentiment_snapshot',   // Customer emotional state at end of session
    ],
  });
 
  // Store with TTL appropriate for expected continuation window
  const handoffToken = await chanl.memory.storeHandoff({
    customerId,
    contextPackage: extraction,
    originChannel: 'voice',
    originSessionId: sessionId,
    destinationChannels,       // which channels this handoff is valid for
    ttl: {
      fullContext: '4h',       // full structured context, 4 hours
      summaryOnly: '72h',      // intent + pending actions only, 3 days
    },
  });
 
  return handoffToken;  // embed this in the SMS/email link
}

The handoff token is what ties the context package to the customer's next session. It gets embedded in the SMS link, the email thread, or the callback URL. When the customer arrives on the new channel, the token resolves to the context package and it gets injected into the session.

Channel-to-channel handoff patterns

The specific mechanism varies by channel combination, but the data flow is the same: serialize context at session end, store it with a token, restore it at session start on the new channel.

Voice to SMS or WhatsApp. After the call ends, the agent sends a message containing the handoff token encoded in a URL. The customer clicks the link, which opens the messaging interface with the token as a query parameter. On session start, the messaging agent resolves the token and loads the context package.

Voice to callback. The customer requests a callback. Your callback scheduling system records the token alongside the callback request. When the callback fires and the agent answers, the context is pre-loaded before the conversation starts. The agent already knows why the customer called.

Chat to email. When a chat session closes with unresolved issues, the agent sends an email summary that includes a "continue this conversation" link with the token embedded. The email itself contains a human-readable summary; the link restores the machine-readable context package.

Async continuation. The customer engaged a week ago via chat, didn't fully resolve their issue, and comes back. If the handoff token is still within its TTL, the agent picks up the summary context. If it's expired, the agent knows there was a previous engagement and can reference it briefly without trying to restore full state.

The pattern for all of these is the same:

Session ends on origin channel Extract structured context package Store with handoff token and TTL Send token to customer via link or callback queue Customer arrives on destination channel Resolve token to context package Inject into destination session context window Surface summary to customer for confirmation Continue from established state
Channel context handoff lifecycle

The storage step is where Chanl's memory layer fits in: it handles the structured storage, TTL management, and token resolution, so you're not building a custom handoff store for each channel combination.

Restoring context at the receiving channel

Loading a context package into a new session is not the same as pasting a transcript. The next agent needs to understand what was established, what's pending, and what emotional context to start from.

The most reliable approach is structured context injection: load the package into labeled blocks in the system prompt, separate from the persona and the behavioral rules.

context-restore.ts·typescript
import Chanl from '@chanl/sdk';
 
const chanl = new Chanl({ apiKey: process.env.CHANL_API_KEY });
 
async function restoreSessionContext(
  handoffToken: string,
  customerId: string
): Promise<string> {
  const pkg = await chanl.memory.resolveHandoff({
    token: handoffToken,
    customerId,  // validate ownership before resolving
  });
 
  if (!pkg) {
    return '';  // no valid handoff, start fresh
  }
 
  // Format the context package for injection into the system prompt
  return `
## Context from previous session (${pkg.originChannel}, ${pkg.timestamp})
 
CUSTOMER INTENT: ${pkg.intent}
 
PROGRESS STATE:
${pkg.progressState.completed.map((s: string) => `- [done] ${s}`).join('\n')}
${pkg.progressState.pending.map((s: string) => `- [pending] ${s}`).join('\n')}
 
ESTABLISHED FACTS:
${pkg.entities.map((e: { key: string; value: string }) => `- ${e.key}: ${e.value}`).join('\n')}
 
PENDING ACTIONS:
${pkg.pendingActions.map((a: string) => `- ${a}`).join('\n')}
 
SENTIMENT: ${pkg.sentiment} (customer was ${pkg.sentimentDetail} at end of previous session)
 
INSTRUCTION: Begin by surfacing a brief summary of the above context for the customer's confirmation. Do not ask the customer to repeat information already captured here.
  `.trim();
}

The last instruction matters. Without it, agents sometimes re-ask questions whose answers are right there in the context package. A brief confirmation step is better: the agent surfaces what it knows and asks whether to proceed from that point.

This is sometimes called the "previously on" pattern. "I have notes from your call this morning about the $43 overcharge on account 4821. You'd agreed to a credit. Want me to check on the status of that?" The customer confirms in one word. The context is verified. The conversation continues without starting over.

Customer service representative

Customer Profile

Contact details & preferences

SC

Sarah Chen

Premium
Company
Acme Corp
Last Call
2 days ago
Preferred
Email
Agent Override
Sales Bot
Custom Attributes
VIPEnterpriseWest Coast

The confirmation step also serves as a quality check for the extraction. If the agent surfaces the wrong entity or mischaracterizes the intent, the customer will correct it immediately. That correction becomes part of the current session's context. You've turned a potential frustration into a demonstration that the agent can be corrected.

Testing cross-channel continuity

Cross-channel continuity breaks in predictable ways. Testing for them is straightforward once you know what to look for.

Re-establishment failures. The next agent asks for information the previous agent already captured. ("Can I get your account number?") This is the most common failure mode and usually indicates the entity extraction step missed a key field, or the context injection wasn't formatted in a way the model recognized as established facts.

Resolved issue re-raising. The next agent attempts to restart a conversation about something the previous agent already resolved. This happens when the progress state extraction is incomplete and the model doesn't know which items are done.

Stale context injection. A customer returns on a different channel long after the TTL should have expired, and the agent tries to pick up a cold context that's now inaccurate or irrelevant.

Identity mismatch. A handoff token gets resolved for the wrong customer because the ownership validation was too permissive.

The way to test these is with multi-session scenarios: run a simulated voice call through 70% of a standard flow, trigger the channel handoff, then simulate the customer continuing on a second channel and verify the agent's behavior. Chanl's scenario testing supports multi-session test runs, which is what you need here -- single-session testing won't catch cross-channel failures because the failure is in the transition, not the individual sessions.

cross-channel-test.ts·typescript
import Chanl from '@chanl/sdk';
 
const chanl = new Chanl({ apiKey: process.env.CHANL_API_KEY });
 
// Multi-session test: voice call leaves a handoff, chat session picks it up
const testResult = await chanl.scenarios.runMultiSession({
  sessions: [
    {
      channel: 'voice',
      scenarioId: 'billing-dispute-partial',  // completes 70% of the flow
      personaId: 'standard-customer',
      captureHandoffToken: true,
    },
    {
      channel: 'chat',
      scenarioId: 'billing-dispute-continuation',  // picks up where voice left off
      personaId: 'returning-customer',
      useHandoffTokenFrom: 'session-1',
    },
  ],
  evaluators: [
    'no-repeat-identification',    // customer didn't re-identify
    'no-repeat-issue-explanation', // customer didn't re-explain the issue
    'pending-actions-resolved',    // promised items were followed up
    'entity-accuracy',             // entities from session 1 were correct in session 2
  ],
});

For monitoring in production, track two signals via your analytics pipeline: the handoff resolution rate (how often tokens are created vs. actually used by customers) and the re-identification rate (how often agents on receiving channels ask for information that should have been in the package). Both signals tell you whether the handoff architecture is actually being used and whether it's working when it is.

When to clear context vs. carry it forward

Not every channel visit should restore previous context. Two scenarios where you should not inject a context package:

Different intent. A customer who called about billing last week and now wants to ask about upgrading their plan doesn't need billing dispute context injected into a sales conversation. Context packages should be scoped to a specific issue or intent, resolved by matching that intent on arrival rather than matching the customer identity alone.

Customer preference. Some customers explicitly want a fresh start. If a previous interaction went badly, they may not want the agent to reference it. A pattern that helps here: on arriving in the new channel, give the customer the option to continue from where they left off or start fresh. Short, clear. Most customers who had a good previous experience will choose to continue; customers who didn't will appreciate the option.

TTL expiration. After the full-context TTL expires, don't try to restore the detailed package. You can keep a high-level summary ("customer had a billing dispute in May, credit was applied") in long-term customer memory, but that's different from injecting a stale context package as if the conversation were continuing. Stale context is worse than no context -- it creates false confidence that the agent knows things it actually doesn't.

The rule for long-term retention is: facts that stay true over time belong in persistent memory, not in a channel context package. Account details, preferences, past resolutions -- these belong in structured customer memory, where they can be retrieved selectively for any future session. The channel context package is for the ephemeral continuation of a specific unfinished conversation.

For a deeper look at how persistent memory and session context work together, the RAM-not-storage mental model covers the two-layer architecture that underpins this pattern.

The experience customers remember

Channel switching is a stress test for your CX infrastructure. It exposes every gap between your platforms and makes the fragmentation visible to the customer at exactly the moment they're already asking for help.

Getting it right means the customer doesn't notice the channel change at all. Their issue carries forward. Their identity carries forward. The context they already provided carries forward. The conversation continues as if the channel boundary never existed.

That's not magic. It's a structured extraction, a stored token, and a careful injection step at the start of the new session. The architecture is straightforward. The experience it creates is the kind customers remember, and the kind that brings them back.

Connect your channels with context that carries forward

Chanl's memory layer handles context package extraction, handoff token storage with TTL management, and structured restoration across voice, chat, and messaging. Your agents stay coherent no matter how the conversation moves.

Explore Memory
DG

Co-founder

Building the platform for AI agents at Chanl — tools, testing, and observability for customer experience.

Learn Agentic AI

Weekly. Patterns for shipping agents that work. MCP, scorecards, regression tests, prompts, model comparisons.

500+ builders subscribed

Frequently Asked Questions