Handoff

Generate a cross-channel briefing for a human agent taking over a conversation.

A handoff briefing is a structured snapshot of everything ConvoMem knows about a customer: their full conversation journey, key memories, commitments already made, sentiment trend, open-issue detection, and a narrative summary. Designed to be injected directly into a human agent's context the moment they take over.

Get a briefing

const handoff = await client.getHandoff({ email: '[email protected]' })
 
if (handoff.found) {
  console.log('Narrative:', handoff.narrative)
  console.log('Key memories:', handoff.keyMemories)
  console.log('Sentiment:', handoff.sentimentTrend.direction)
  console.log('Open issue:', handoff.openIssue.isOpen)
}

Options

OptionTypeDefaultDescription
freshbooleanfalseRebuild the brief instead of serving the cached copy
narrativebooleantrueSet to false to skip the narrative — faster, structured data only

Briefs are cached for up to 24 hours by default and rebuilt automatically whenever a conversation ends or is escalated, or the open issue is triaged. Turns captured in a conversation that is still active do not refresh the cache, so pass fresh: true when you need them in the brief.

const handoff = await client.getHandoff(
  { customerId: 'f3b39d1b-2522-4a00-98df-8cdde783ba12' },
  { fresh: true, narrative: true },
)

Response fields

FieldTypeDescription
foundbooleanfalse only when no customer matches the identity; the response is then { found: false, customer: null }
customerobjectProfile summary: id, name, email, phone, externalId, lastContactAt, lastSentiment
journeyobject[]Every conversation, oldest first: conversationId, channel, status, startedAt, endedAt, summary, outcome, sentiment, messagesCount
keyMemoriesobject[]Customer-stated facts ranked by relevance to the open issue: id, content, category, memoryType, sentiment, conversationId, channelSource, relevantToIssue
agentCommitmentsobject[]What your side already committed to (same shape as keyMemories), kept separate from the customer's own statements; empty unless agent capture is enabled
sentimentTrendobject{ direction, current, points } — see below
openIssueobject{ isOpen, reason, conversationId, summary, priority, tags, snoozedUntil, resolvedAt, insights, effectivePriority }; reason is ESCALATED, ACTIVE, OUTCOME_UNRESOLVED, or null
recentInsightsobject[]Open insights for the customer: id, insightType, content, status, confidence, conversationId, createdAt
notesobject[]Internal agent notes, newest first: id, body, authorId, conversationId, createdAt
narrativestring | nullNarrative summary of the customer's history; null when narrative=false
narrativeSourcestringHow the narrative was produced — see below
generatedAtstringISO-8601 time the brief was built
cachedbooleantrue when served from the cache

sentimentTrend — direction is improving, declining, stable, or unknown (no conversation has a sentiment score yet). current is the latest sentiment score (or the customer's last known score, or null). points lists each scored conversation as { at, channel, sentiment }, oldest first.

narrativeSource

ValueMeaning
llmWritten by the model
pendingA deterministic summary served now while the model-written version is generated in the background; a later request returns llm
fallbackThe model was unavailable, so a deterministic summary is used
skippedYou passed narrative=false

Injecting into a human agent

agent-context.ts
const handoff = await client.getHandoff(
  { customerId: escalatedCustomerId },
  { fresh: true },
)
 
const briefing = `
## Customer briefing
 
${handoff.narrative ?? 'No prior history.'}
 
### Key facts
${handoff.keyMemories.map((m) => `- ${m.content}`).join('\n')}
 
### Sentiment
${handoff.sentimentTrend.direction} (score: ${handoff.sentimentTrend.current ?? 'n/a'})
 
### Open issue
${handoff.openIssue.isOpen ? handoff.openIssue.summary : 'None'}
`.trim()

By customer ID (path-based route)

For direct ID access, the SDK routes to the path-based endpoint:

// With customerId → GET /api/v1/customers/:id/handoff
const byId = await client.getHandoff({ customerId: 'f3b39d1b-2522-4a00-98df-8cdde783ba12' })
 
// Without customerId → GET /api/v1/customers/handoff?email=…
const byEmail = await client.getHandoff({ email: '[email protected]' })

Route a live transfer

POST /handoff/route answers the question a voice platform asks mid-call: which human destination should take this customer? The decision is made ahead of time, when the customer's last conversation ends, so the call is a single fast read. It is designed to always return 200: an unknown customer or missing decision falls back to the agent's default destination. Read source (memory, default, none, or invalid_request) to see which tier answered.

Handoff destinations are configured per agent in the dashboard; they cannot be created or edited with an API key.

const { destination, source, reasoning } = await client.routeHandoff({
  phone: '+14155550101',
})
 
if (destination) {
  await telephony.transfer(destination.value, destination.transferMessage)
}

The response is { destination, source, reasoning, customerId }, where destination is { id, name, type, value, transferMessage } (type is PHONE or SIP) or null when no destination is configured. Add ?format=dograh to receive Dograh's transfer_context shape instead.

What's next

  • Customers — manage the profiles behind briefings
  • Conversations — manage and escalate sessions
  • Embed — the hosted agent panel for browser-based handoff

SDK: TypeScript · Python · Rust