Skip to content

Handoff Trip → Agentforce (agente SDR)

Status: implementado atrás da flag ENABLE_AGENTFORCE_HANDOFF (default false). Contrato técnico da Agent API: docs/agentforce/DAS_Integracao_A2A_IFriend.docx (Everymind, v1.1 — 23/09/2026). Os anexos da Everymind (docs/agentforce/) ficam só no repositório — são excluídos do site de docs.

1. Objetivo

Hoje todo handoff humano do Trip termina no formulário de WhatsApp (gerar_formulario_suporte): o cliente cai direto no time humano do Service Console, qualificado ou não.

Com esta integração, nos handoffs o Trip escala primeiro para o agente SDR no Agentforce, que qualifica a oportunidade e decide:

  • abrir um fluxo de Vendas para o time comercial; ou
  • informar ao cliente que não há o produto.

O time humano passa a receber só cases qualificados.

Escopo desta fase

Canal Primeiro atendimento Nesta fase
Site (webchat/SSE) Trip ✅ Trip → Agentforce (este documento)
WhatsApp oficial (Digital Engagement) Agentforce Everymind: Agentforce → Trip para busca de produto, via o servidor A2A de entrada do Trip (contrato de contexto na seção 9)

2. Visão geral

flowchart LR
    C[Cliente no site] -->|mensagens| T[Trip<br/>root_agent + sub-agentes]
    T -->|busca produtos| CAT[(Catálogo iFriend)]
    T -->|M1 · M2 · M3<br/>escalar_agentforce| AF[Agentforce<br/>agente SDR]
    AF -->|qualifica| D{Decisão}
    D -->|oportunidade| V[Fluxo de Vendas<br/>time comercial]
    D -->|sem produto| C
    T -.->|fallback: API fora| W[Formulário WhatsApp<br/>Service Console]

Modo relay: o cliente continua no chat do site. Depois do handoff, o Trip repassa cada mensagem do cliente para o Agentforce e mostra as respostas dele. O LLM do Trip fica fora do caminho até o Agentforce encerrar a sessão.

3. Os momentos em que o Trip chama o Agentforce

Pré-condição comum: identificação da conta

O Agentforce precisa saber quem é o cliente e de que tipo é a conta para abrir o Case e dar sequência. A conta é criada no Salesforce (pelo fluxo do Case). O Trip só identifica e coleta os dados mínimos, e nunca cria conta na iFriend.

Tipo tipo_conta Dados obrigatórios
Viajante (B2C) viajante nome, e-mail, telefone/WhatsApp
Agência / empresa (B2B) agencia nome do contato, e-mail, telefone/WhatsApp, nome da empresa e CNPJ
Situação Comportamento do Trip
Logado (JWT) Nome e e-mail vêm do jwt_context. O tipo vem das roles: ROLE_AFFILIATED* ou partner_code é agência; senão, viajante. A conta conta como existente. O Trip pede o telefone e, se for agência, empresa + CNPJ (o JWT não traz esses dados).
Anônimo com conta Pergunta "Você já tem conta na iFriend? Qual o e-mail?" → buscar_usuario(email) (somente leitura). Se encontrar, conta_existente=true e ifriend_id é enviado.
Anônimo sem conta Pergunta se a viagem é pessoal ou para uma agência/empresa e coleta os dados mínimos do tipo em uma única pergunta. A conta segue como nova, e o Salesforce a cria.
Agência (qualquer caso) Com o CNPJ, buscar_agencia(cnpj) (somente leitura) diz se a agência já existe na iFriend.

Validações na tool escalar_agentforce:

  • Recusa sem os dados obrigatórios do tipo (status="dados_incompletos", com a lista faltando).
  • CNPJ validado: 14 dígitos e dígitos verificadores. Se for inválido, pede para o cliente conferir.
  • O tipo vindo do JWT tem precedência sobre o que o LLM informar.

Tabela de momentos

# Momento Onde nasce no Trip Gatilho handoff_reason Contexto enviado
M1 Produto não encontrado discovery_agent, custom_tour_agent A busca não retorna nada que atenda ao pedido → o Trip oferece "Quer que eu te conecte com um especialista iFriend para verificar essa opção?" → o cliente aceita produto_nao_encontrado destino, datas, nº de pessoas, termo buscado, tipo de produto
M2 Pedido explícito de humano support_agent (rota "falar com atendente/consultor/humano/WhatsApp") O cliente pede atendimento humano pedido_humano (ou orcamento se a conversa era de orçamento) tópico, produto/ID se houver, resumo
M3 Tour personalizado / orçamento custom_tour_agent (HANDOFF PARA HUMANO); quote_agent via root_agent → support_agent Cliente sem acesso ao PDF (tem_acesso=false) terminou de montar o pedido, ou pediu consultor tour_personalizado / orcamento briefing estruturado de gerar_briefing_tour + produtos selecionados (ID, nome, preço, moeda)

Case "Fluxo de Ocorrência de Vendas" (JSON estruturado)

Em todo handoff (M1, M2 e M3) o Trip monta o Case com os campos que capturou na conversa e o envia pronto no formato do Composite Upsert do Salesforce, com os API names reais do objeto Case. Referências: docs/agentforce/Fluxo_de_ocorrencia_de_vendas.docx (campos do fluxo), Case_Fields.csv / Case_Object_Describe.json (API names e picklists) e Exemplo_API_Composite_Upsert_Object.md (formato).

  • M1/M3 (produto_nao_encontrado, tour_personalizado, orcamento): os campos obrigatórios (*) precisam estar preenchidos. Sem eles, a tool devolve dados_incompletos, e o Trip pergunta o que falta em no máximo 2 mensagens.
  • M2 (pedido_humano): o Case vai parcial, com o que já se sabe. Só a identificação é obrigatória.
  • Os campos "preenchido automaticamente" do Salesforce (Account Owner, Região, Priority, Qualificação, Total de Pax...) e os lookups (Solicitante__c, Lead__c, AccountId) não são enviados.

Upsert: PATCH /services/data/v62.0/composite/sobjects/Case/External_ID__c, com o corpo em Sales_Case_Payload. External_ID__c = {sessão do Trip}:{uuid8}, a mesma externalSessionKey da sessão no Agentforce. Isso torna o upsert idempotente e rastreável até a conversa no Trip.

Seção (docx) Campo API name Tipo / valores Obrig. Origem
— Record Type RecordTypeId 012U4000001VcQDIA0 (Fluxo de Vendas; env AGENTFORCE_CASE_RECORD_TYPE_ID) * Tool
— External ID External_ID__c chave externa da sessão * Tool
Description Subject Subject ex.: Cotação — Paris — 3 pax — 11/2026 * Tool
Description Description Description resumo da conversa * Tool
Entrada Case Reason Reason Cotação * Tool
Entrada Case Origin Origin Chat Tool
Entrada Tipo de Registro de Conta Tipo_registro_conta__c B2C (viajante) / B2B (agência) Tool
Entrada CNPJ CNPJ__c 14 dígitos * B2B Identificação
Entrada Contact Name Nome__c nome do contato Identificação
Entrada ID da Reserva ID_da_Reserva_mais_de_uma__c texto Trip
Conta não identificada Web Name / Email / Phone / Company SuppliedName, SuppliedEmail, SuppliedPhone, SuppliedCompany só quando a conta é nova Identificação
Qualificação Destino Destino__c texto * Trip
Qualificação Destinos adicionais Destinos_adicionais__c texto (separado por vírgula) Trip
Qualificação Data início da viagem Data_da_Viagem__c date * Trip
Qualificação Serviços solicitados Servicos_solicitados__c multipicklist: Guia, Guia com carro, Ingresso_ticket, Experiência Regular, Experiencia_privativa, Transfer, Veículo à disposição, Outros Trip (mapeado)
Qualificação Já comprou as passagens? Ja_comprou_as_passagens__c Sim / Não / Não respondeu * Trip
Qualificação Já reservou a hospedagem? J_comprou_a_hospedagem__c Sim / Não / Não respondeu * Trip
Qualificação Valor/Orçamento predefinido Valor__c currency (texto sem número vai para Informações adicionais) Trip
Mais informações Quantidade de adultos / crianças Quantidade_de_adultos__c, Quantidade_de_crian_as__c número Trip
Mais informações Já possui roteiro Ja_possui_roteiro__c Sim / Não Trip
Mais informações Serviços privativos ou em grupo Tem_prefer_mcia__c Privativo / Grupo Trip
Mais informações Possui necessidade especial / Qual Possui_alguma_necessidade_especial__c, Necessidades_especiais__c Sim/Não + texto (só se Sim) Trip
Mais informações Produto de interesse Produto__c texto Trip
Exclusivo Transfer Origem/Destino/Data/Hora Ida e Volta, malas Origem_Ida__c, Destino_Ida__c, Data_Ida_viagem__c, Hora_Ida_da_Viagem__c, Origem_volta__c, Destino_Volta__c, Data_Volta_da_Viagem__c, Hora_Volta_viagem__c, Quantidade_de_malas_pequenas__c, Quantidade_de_malas_grandes__c só quando o interesse é transfer Trip
— Informações adicionais Informa_es_adicionais__c motivo do handoff, sessão/canal, conta nova/existente e IDs iFriend, produtos do catálogo considerados Tool

O LLM do Trip preenche um JSON de captura (chaves em PT: destino, data_inicio_viagem, ja_comprou_passagens...; ver a docstring de escalar_agentforce). A tool valida, completa e converte para os API names acima (integrations/agentforce/sales_case.py). Um teste confere cada campo e cada valor de picklist contra o Case_Object_Describe.json.

Exemplo de Sales_Case_Payload (agência nova, produto não encontrado):

{
  "allOrNone": true,
  "records": [
    {
      "attributes": {"type": "Case"},
      "RecordTypeId": "012U4000001VcQDIA0",
      "External_ID__c": "sse_u1_ab12cd34:9f3c1a2b",
      "Subject": "Cotação — Capadócia — 2 pax — 05/2026",
      "Description": "Procura passeio de balão na Capadócia para 2 pessoas em maio",
      "Reason": "Cotação",
      "Origin": "Chat",
      "Tipo_registro_conta__c": "B2B",
      "CNPJ__c": "11222333000181",
      "Nome__c": "Ana Souza",
      "SuppliedName": "Ana Souza",
      "SuppliedEmail": "ana@viajebem.com.br",
      "SuppliedPhone": "5511999990000",
      "SuppliedCompany": "Agência Viaje Bem",
      "Destino__c": "Capadócia",
      "Data_da_Viagem__c": "2026-05-10",
      "Servicos_solicitados__c": "Experiência Regular",
      "Ja_comprou_as_passagens__c": "Sim",
      "J_comprou_a_hospedagem__c": "Não",
      "Quantidade_de_adultos__c": 2,
      "Quantidade_de_crian_as__c": 0,
      "Produto__c": "passeio de balão",
      "Informa_es_adicionais__c": "Origem: Trip (assistente do site) — motivo do handoff: produto_nao_encontrado\nSessão Trip: sse_u1_ab12cd34 | canal: sse\nConta: agencia — nova"
    }
  ]
}

Transporte: o corpo acima vai inteiro em uma única variável de contexto (Sales_Case_Payload, nome configurável em AGENTFORCE_CASE_VARIABLE). A 1ª mensagem de texto leva um resumo legível (seção 5). Se decidir abrir o Fluxo de Vendas, o Agentforce faz o upsert com esse corpo, sem mapear campo a campo.

Regra de acesso: affiliate/admin não faz handoff de orçamento

Quem tem resolve_proposal_access(jwt_context) == True (ROLE_AFFILIATED_PARENT, ROLE_AFFILIATED_EMPLOYEE, ROLE_*_ADMIN) gera a proposta sozinho pelo proposal_agent/gerar_proposta.

  • M3 nunca dispara para esse usuário. Os prompts só escalam com tem_acesso=false, e a própria tool devolve status="nao_permitido" para orcamento/tour_personalizado (defesa em profundidade, no mesmo padrão do gate de gerar_proposta).
  • M1 e M2 continuam disponíveis para qualquer usuário: produto inexistente e pedido explícito de humano.

Quando o Trip NÃO chama o Agentforce (usa o formulário de WhatsApp como antes)

  • Sites whitelabel. O atendimento humano é o WhatsApp do affiliate, não o SDR iFriend (AGENTFORCE_ALLOW_WHITELABEL=false por padrão).
  • Canal fora de AGENTFORCE_CHANNELS (padrão sse,webchat).
  • Agentforce indisponível ou já com falha nesta conversa (ver seção 6).
  • Flag desligada ou credenciais ausentes.

4. Sequência por momento

M1 — Produto não encontrado (site)

sequenceDiagram
    autonumber
    actor C as Cliente
    participant D as discovery_agent
    participant T as escalar_agentforce
    participant R as root_agent (relay)
    participant AF as Agentforce (SDR)

    C->>D: "Passeio de balão na Capadócia em maio, 2 pessoas"
    D->>D: busca_produtos → []
    D-->>C: Não encontrei online. Quer que eu te conecte com um especialista?
    C->>D: Sim
    D-->>C: Você já tem conta na iFriend? Qual o e-mail?
    C->>D: Não tenho
    D-->>C: A viagem é pessoal ou para uma agência/empresa?
    C->>D: Agência — Ana, ana@viajebem.com.br, 11 99999-0000, Viaje Bem, CNPJ 11.222.333/0001-81
    D->>D: buscar_agencia(cnpj) → não encontrada (conta nova)
    D-->>C: Qual a data de início e quantos adultos/crianças?
    C->>D: 10/05, 2 adultos
    D-->>C: Já comprou as passagens? E a hospedagem?
    C->>D: Passagens sim, hotel ainda não
    D->>T: escalar_agentforce(produto_nao_encontrado, conta, case_vendas_json)
    T->>T: valida conta (CNPJ) e campos * do Case
    T->>AF: POST /agents/{id}/sessions (variables: Sales_Case_Payload)
    T->>AF: POST /sessions/{sid}/messages (seq 1: resumo do contexto)
    AF-->>T: resposta
    T->>R: transfer_to_agent(root_agent)
    R-->>C: resposta do Agentforce
    loop até SessionEnded / inatividade
        C->>R: mensagem
        R->>AF: POST /messages (seq n)
        AF-->>R: resposta
        R-->>C: resposta
    end
    AF-->>R: SessionEnded (oportunidade aberta ou sem produto)
    R-->>C: última mensagem — o Trip volta a atender

M2 — Pedido explícito de humano

Igual ao M1, mas começa no support_agent: o root_agent roteia para ele → identificação da conta → escalar_agentforce(pedido_humano) com o Case parcial (o que já se souber da conversa, sem interrogatório).

M3 — Tour personalizado / orçamento sem acesso ao PDF

custom_tour_agent: ETAPA 1 (verificar_acesso_orcamento → tem_acesso=false) → ETAPA 2 (produtos reais selecionados nos cards) → nº de participantes → gerar_briefing_tour → identificação da conta → campos * do Case que faltarem (data de início, passagens, hospedagem) → escalar_agentforce(tour_personalizado|orcamento, case_vendas_json=briefing+produtos_catalogo) → relay.

5. Contrato com o Agentforce

Chamadas (conforme a DAS)

# Chamada Quando o Trip faz
1 POST https://{my_domain}.my.salesforce.com/services/oauth2/token (client_credentials) Na 1ª chamada, e de novo quando o token vence ou a API responde 401 (1 retry)
2 POST https://api.salesforce.com/einstein/ai-agent/v1/agents/{agent_id}/sessions No handoff: 1 sessão por handoff
3 POST .../sessions/{session_id}/messages seq 1 = contexto do Trip; seq 2..n = mensagens do cliente
4 DELETE .../sessions/{session_id} (x-session-end-reason) Inatividade (Timeout), Escalation (Transfer)
  • externalSessionKey = {session_id do Trip}:{uuid8}, rastreável até a conversa no Trip.
  • bypassUser: true, streamingCapabilities.chunkTypes: ["Text"] (modo síncrono; streaming fica para depois).

Primeira mensagem (seq 1): contexto do Trip

[CONTEXTO DO TRIP — handoff automático do assistente do site iFriend]
Motivo do handoff: produto_nao_encontrado
Canal: site
Conta: Agência (B2B) — nova (não encontrada na iFriend)
Empresa: Agência Viaje Bem | CNPJ: 11222333000181
Contato: Ana Souza | ana@viajebem.com.br | 5511999990000
Resumo: Procura passeio de balão na Capadócia para 2 pessoas em maio

Case: Cotação — Capadócia — 2 pax — 05/2026
Destino: Capadócia
Início da viagem: 2026-05-10
Adultos: 2
Produto de interesse: passeio de balão
Passagens compradas: sim
Hospedagem reservada: não
(Case completo, pronto para upsert, na variável de contexto Sales_Case_Payload)

A partir da próxima mensagem você conversa diretamente com o cliente
(as mensagens dele serão repassadas pelo Trip).

Variáveis de contexto (variables)

Variável Enviada Conteúdo
Sales_Case_Payload (env AGENTFORCE_CASE_VARIABLE) Sempre (vazio na env = desliga) Corpo do Composite Upsert do Case (seção 3)
Variáveis avulsas (opcional) Só as mapeadas em AGENTFORCE_CONTEXT_VARIABLES Ver tabela abaixo

Mapeamento opcional de variáveis avulsas, configurável sem deploy de código:

AGENTFORCE_CONTEXT_VARIABLES='{"handoff_reason":"Handoff_Reason","customer_name":"Customer_Name",...}'
Chave no Trip API name proposto Conteúdo
trip_session_id Trip_Session_Id ID da conversa no Trip
handoff_reason Handoff_Reason produto_nao_encontrado / pedido_humano / tour_personalizado / orcamento
account_type Account_Type viajante / agencia
customer_name Customer_Name Nome
customer_email Customer_Email E-mail
customer_phone Customer_Phone Telefone/WhatsApp
summary Summary Resumo da conversa
channel Channel sse / webchat
whitelabel Whitelabel URL do whitelabel (quando aplicável)

Como o Trip interpreta as respostas

type da mensagem Ação do Trip
Inform Mostra o texto ao cliente e segue no relay
SessionEnded Mostra o último texto (se houver) e sai do relay; o Trip volta a atender
Escalation Sai do relay, faz o DELETE da sessão e orienta o cliente a pedir atendente (formulário de WhatsApp)
Failure Registra em log e ignora

6. Ciclo de vida do relay e fallback

Estado em session.state["agentforce"]:

{
  "active": true, "failed": false,
  "session_id": "sf-...", "external_key": "trip-sess:ab12cd34", "seq": 3,
  "reason": "produto_nao_encontrado", "started_at": 0, "last_activity": 0,
  "pending_reply": null, "last_relayed_invocation_id": "..."
}
Evento Resultado
Handoff aberto active=true; a resposta ao contexto é exibida na mesma resposta do Trip
Mensagem do cliente com relay ativo Repassada (seq+1); o LLM do Trip não roda
SessionEnded active=false
Sem mensagens por AGENTFORCE_IDLE_MINUTES (30) DELETE (Timeout), active=false; a mensagem atual segue para o Trip
Erro na Agent API durante o relay (4xx/5xx/timeout/circuit breaker) active=false, failed=true; o cliente recebe um aviso e, ao pedir atendente, recebe o formulário de WhatsApp
Erro ao abrir o handoff Fallback imediato para o formulário de WhatsApp (comportamento anterior)
Arquivo/mídia durante o relay Envia um aviso textual ao Agentforce (a Agent API está configurada só para texto)

Proteções do cliente HTTP: cache de token, 1 retry em 401, circuit breaker (3 falhas → 60s aberto), timeout de AGENTFORCE_TIMEOUT (60s).

7. Implementação no Trip

Por que tool + callback, e não um novo sub-agente:

  • Relay determinístico. No relay quem fala é o Agentforce. Um sub-agente LLM reescreveria cada turno, com mais latência e custo e com risco de distorcer a qualificação.
  • O contexto já está nos agentes que disparam o handoff. Transferir para outro agente perderia o contexto estruturado, e disallow_transfer_to_peers=True bloqueia a transferência entre irmãos.
  • Mesmo padrão já usado pelo gerar_formulario_suporte.
Arquivo Papel
ifriend_agent/integrations/agentforce/client.py AgentforceClient: OAuth, sessões, mensagens, DELETE, 401 retry, circuit breaker
ifriend_agent/integrations/agentforce/handoff.py Estado do relay, mensagem de contexto, variables, regras de canal/whitelabel
ifriend_agent/tools/escalar_agentforce_tool.py Tool do handoff (identificação da conta, Case, gate de acesso, fallback)
ifriend_agent/integrations/agentforce/account.py Tipo de conta pelo JWT, validação de CNPJ
ifriend_agent/integrations/agentforce/sales_case.py Modelo SalesCase (pydantic), obrigatoriedade por motivo, subject/resumo
ifriend_agent/callbacks/agentforce_relay_callback.py Relay no before_agent_callback do root_agent
ifriend_agent/prompts/agentforce_handoff_prompt.py Trechos de prompt (support, discovery, custom_tour)
examples/agentforce_handoff_cli.py CLI de teste contra a sandbox
ifriend_agent/tests/agentforce/ Testes unitários

8. Configuração e deploy

Variável Default Descrição
ENABLE_AGENTFORCE_HANDOFF false Liga o handoff via Agentforce
AGENTFORCE_MY_DOMAIN_URL — My Domain da org (URL do token)
AGENTFORCE_INSTANCE_ENDPOINT instance_url do OAuth instanceConfig.endpoint
AGENTFORCE_AGENT_ID — ID 0Xx... do agente SDR
AGENTFORCE_CLIENT_ID — Consumer Key
AGENTFORCE_CLIENT_SECRET — Consumer Secret (Secret Manager)
AGENTFORCE_TIMEOUT 60 Timeout HTTP (s)
AGENTFORCE_TOKEN_LIFETIME_MINUTES 20 Cache do token (o 401 renova de qualquer forma)
AGENTFORCE_IDLE_MINUTES 30 Inatividade até encerrar a sessão
AGENTFORCE_CHANNELS sse,webchat Canais com handoff via Agentforce
AGENTFORCE_ALLOW_WHITELABEL false Whitelabel também escala ao SDR iFriend
AGENTFORCE_CASE_VARIABLE Sales_Case_Payload Variável de contexto que recebe o corpo do Composite Upsert do Case (vazio = não envia)
AGENTFORCE_CASE_RECORD_TYPE_ID 012U4000001VcQDIA0 RecordTypeId do Case "Fluxo de Vendas" (conferir por org)
AGENTFORCE_CONTEXT_VARIABLES — Mapeamento para variables avulsas (seção 5)

Checklist de deploy (stage):

  1. Criar o secret: gcloud secrets create trip-ifriend-agentforce-client-secret-stage + versão com o Consumer Secret (ver runtime/scripts/create-secrets.sh).
  2. Em cloudbuild.stage.yaml, adicionar AGENTFORCE_CLIENT_SECRET=trip-ifriend-agentforce-client-secret-stage:latest ao --set-secrets e um --set-env-vars com ENABLE_AGENTFORCE_HANDOFF, AGENTFORCE_MY_DOMAIN_URL, AGENTFORCE_AGENT_ID, AGENTFORCE_CLIENT_ID. Só depois do passo 1: um --set-secrets apontando para um secret inexistente quebra o deploy.
  3. Validar com examples/agentforce_handoff_cli.py antes de ligar a flag.
  4. Produção: External Client App próprio (DAS §10), com secret e Agent ID de produção.

9. Agentforce → Trip via A2A (WhatsApp)

No WhatsApp o primeiro atendimento é do Agentforce, que chama o Trip pelo servidor A2A de entrada (JSON-RPC, JWT com ROLE_A2A_USER) para buscar produtos. O protocolo A2A não define onde vai o contexto do chamador, então o Trip publica o próprio contrato no Agent Card (/a2a/.well-known/agent-card.json): a extensão https://theifriend.com/a2a/extensions/caller-context/v1, opcional.

Onde o Agentforce pode mandar o contexto, em ordem de precedência:

  1. DataPart em message.parts ({"kind": "data", "data": {...}}). Preferido.
  2. message.metadata, com as chaves soltas ou aninhadas sob a URI da extensão.
  3. params.metadata.
{
  "jsonrpc": "2.0", "id": "1", "method": "message/send",
  "params": {
    "message": {
      "role": "user", "messageId": "…", "contextId": "sessao-agentforce-123",
      "parts": [
        {"kind": "text", "text": "Cliente quer passeio em Lisboa dia 12/11"},
        {"kind": "data", "data": {
          "caller": "agentforce", "caseId": "500…", "contactId": "003…",
          "canal": "WhatsApp", "idioma": "pt-BR",
          "cliente": {"nome": "Maria", "email": "maria@x.com", "telefone": "5511999990000"},
          "conta": {"tipo": "viajante", "existente": true}
        }}
      ]
    }
  }
}

Como o Trip trata essa chamada:

  • O contexto é normalizado (aceita caseId/case_id, channel/canal, language/idioma...) e gravado em session.state["a2a_inbound_context"] antes do agente rodar.
  • O LLM vê só uma linha de resumo ("[Contexto do solicitante (via A2A): cliente Maria, canal WhatsApp, idioma pt-BR]"), e não o JSON bruto.
  • Payload no estilo protobuf/A2A v1 ("role": "ROLE_USER", content, parts sem kind, métodos SendMessage/SendStreamingMessage) é normalizado para o formato v0.3 antes da validação.
  • Sem handoff quando o chamador é outro agente. O Trip não pede de novo os dados que já vieram e não escala para humano. Se não houver produto, responde isso claramente, e o Agentforce decide.
  • Sem contexto nenhum, o Trip funciona só com o texto.

Detalhes para parceiros: Integração A2A → Para Parceiros.

10. Pontos em aberto (para a Everymind)

  1. Variável Sales_Case_Payload: criar no agente. Ela recebe o corpo do Composite Upsert do Case, já com os API names (seção 3). Falta confirmar quem executa o upsert (Agentforce/Flow) e se o RecordTypeId 012U4000001VcQDIA0 é o mesmo em sandbox e produção.
  2. API names das variáveis avulsas (seção 5), se forem usadas.
  3. Sinal da decisão final. Como o agente indica "Case/oportunidade aberta" vs "sem produto"? Hoje o Trip depende de SessionEnded. O agente vai encerrar a sessão ao decidir, ou deve devolver uma mensagem estruturada/variável?
  4. Escalation via Agent API. Quando o agente quiser transferir para humano (Omni-Channel), o que acontece numa sessão da Agent API? O Trip hoje encerra a sessão e oferece o WhatsApp.
  5. My Domain e Agent ID de produção, e confirmação de qual org responde pelo token e pelo instanceConfig.endpoint (na collection da sandbox são hosts diferentes).
  6. Deduplicação de conta/Case quando o mesmo cliente vier depois pelo WhatsApp (e-mail, telefone ou CNPJ como chave?).
  7. A2A (WhatsApp): confirmar se o Agentforce envia o contexto como DataPart (seção 9) e em qual versão do protocolo. Vale capturar uma requisição real no sandbox.

11. Segurança

  • Credenciais (client_id/client_secret) só no Secret Manager / variáveis secretas do Postman — nunca em código, docs ou collections exportadas. Um External Client App por ambiente.
  • Nada do Agentforce chega ao LLM do Trip além do texto das respostas. O contexto enviado contém nome, e-mail e telefone informados pelo próprio cliente para o atendimento.
  • O Trip nunca menciona "Agentforce", "Salesforce" ou "SDR" ao cliente, só "nosso especialista".