O deploy passou, mas uma execução antiga voltou com os argumentos da tool v1. O novo handler espera subject e priority; o payload persistido ainda traz title e details. O erro aparece só quando alguém retoma a execução, depois que a mudança já está em produção.
Para evitar esse tipo de quebra, trate a versão do contrato como parte da execução. Registre qual versão da tool foi escolhida, valide a entrada na fronteira, adapte versões antigas em um lugar só e pare antes do efeito quando a versão for desconhecida. Não deixe cada handler adivinhar se um payload antigo ainda é seguro.
Isso é diferente de apenas validar uma chamada. A validação de tool calls em TypeScript ajuda a impedir argumentos inválidos. Este artigo trata da mudança do contrato ao longo do tempo, enquanto filas, checkpoints e contexto antigo continuam vivos.

Resposta curta
- Registre a versão do contrato de entrada e de saída junto da execução.
- Faça cada nova execução escolher uma versão explícita.
- Mantenha um adaptador para versões antigas enquanto ainda houver trabalho vivo.
- Valide e adapte antes de chamar o handler que produz efeitos externos.
- Rejeite versões desconhecidas e retire o adaptador antigo somente depois de drenar as execuções.
O que quebra quando uma tool muda?
Uma tool exposta a um modelo tem mais partes do que sua função. O nome, a descrição, o schema de entrada, o formato do resultado e as regras de efeito influenciam o que o agente pode pedir e como o runtime interpreta a resposta. No MCP Tools specification, uma definição de tool inclui name, description, inputSchema e, opcionalmente, outputSchema. No function calling da OpenAI, o schema também faz parte da definição enviada ao modelo.
Uma mudança pode ser estrutural ou semântica:
| Mudança | Risco para uma execução antiga |
|---|---|
| Adicionar um campo opcional | Geralmente baixo, se o handler antigo ignora o campo e o novo caminho aceita a ausência. |
Renomear title para subject |
Alto. O payload antigo não contém o campo que o novo handler lê. |
Transformar status de string em objeto |
Alto. O JSON pode ser válido, mas o significado mudou. |
| Remover uma opção de enum | Alto para tarefas persistidas ou chamadas em fila. |
Alterar o efeito de send_email sem mudar o schema |
Muito alto. A forma parece compatível, mas a autorização aprovada pode não cobrir o novo comportamento. |
O schema do provedor pode impedir uma parte dos argumentos inválidos. Ele não sabe, sozinho, se uma execução criada ontem pode usar a política de hoje. Essa é uma decisão do runtime da aplicação.
O contrato deve viajar com a execução
Guarde pelo menos três identificadores separados:
agentRelease: composição do agente, incluindo instruções, modelo, conjunto de tools e políticas.toolContractVersion: versão do contrato que a execução usou para escolher e montar a chamada.runId: identidade da execução que precisa ser retomada, repetida ou investigada.
Não use um campo único chamado version para representar tudo. Uma nova descrição de tool pode não exigir migração do estado. Uma política nova pode exigir nova aprovação mesmo quando o JSON continua igual. Um deploy pode trocar o código sem trocar o contrato.
Um registro mínimo pode ter esta forma:
{
"runId": "run_7f2",
"agentRelease": "support-agent-2026-10-29.1",
"toolContractVersion": 1,
"status": "waiting_for_tool",
"nextTool": "create_ticket"
}
O runId não é um controle de compatibilidade. Ele permite localizar o trabalho. A versão do contrato diz qual interpretação deve ser aplicada antes da execução.
Onde colocar o adaptador de compatibilidade?
Coloque a adaptação entre a mensagem recebida e o handler da tool. A fronteira deve receber um envelope com versão, validar a forma daquela versão e produzir um único formato interno para o handler atual.
O trecho abaixo é ilustrativo e não substitui um validador de schema. Ele mostra a separação importante: o handler de negócio não precisa conhecer todos os formatos históricos.
type ToolCall = {
name: "create_ticket";
contractVersion: 1 | 2;
arguments: unknown;
};
type CreateTicketV1 = {
title: string;
details: string;
};
type CreateTicketV2 = {
subject: string;
body: string;
priority: "low" | "normal" | "high";
};
function adaptCreateTicket(call: ToolCall): CreateTicketV2 {
if (call.contractVersion === 1) {
const oldArgs = call.arguments as CreateTicketV1;
return {
subject: oldArgs.title,
body: oldArgs.details,
priority: "normal",
};
}
if (call.contractVersion === 2) {
return call.arguments as CreateTicketV2;
}
throw new Error("Unsupported tool contract version");
}
async function execute(call: ToolCall) {
const args = adaptCreateTicket(call);
// Valide `args` antes deste ponto em uma implementação real.
return createTicket(args);
}
Em código de produção, valide arguments contra o schema da versão antes do cast, limite quais versões cada release consegue atender e registre a versão resolvida no trace. A documentação da OpenAI recomenda strict: true para que chamadas de função sigam o schema fornecido. Isso reduz chamadas malformadas, mas a compatibilidade entre v1 e v2 ainda precisa existir no seu sistema.
O adapter também é o lugar para decidir se uma resposta antiga pode voltar ao agente. Se a v2 retorna { ticketId, url } e uma execução v1 espera { id }, adapte a saída ou mantenha o worker antigo. Não envie um objeto novo para um consumidor antigo só porque ambos são JSON válido.
Quatro decisões para cada versão viva
Depois de identificar uma chamada antiga, escolha uma política explícita:
| Situação | Política | Ação |
|---|---|---|
| O contrato antigo continua seguro | Pin | Execute com o worker e schema antigos até a execução terminar. |
| A estrutura mudou, mas o significado permanece | Adapt | Valide a versão antiga, converta na fronteira e execute o handler atual. |
| O estado pode ser convertido sem perder decisão ou efeito | Migrate | Faça uma transformação registrada, valide o novo estado e marque a versão resultante. |
| A versão não é conhecida ou a semântica mudou | Reject | Pare antes do efeito, registre o motivo e peça intervenção ou replanejamento. |
O erro mais perigoso é confundir adapt com migrate. Adaptar é uma conversão na entrada ou na saída de uma chamada. Migrar altera o estado persistido da execução. Se uma aprovação humana foi dada para os argumentos antigos, converter o JSON não prova que a aprovação vale para os novos argumentos.
Sistemas de workflow fazem uma distinção parecida. A documentação do Temporal sobre versionamento de workflows separa workers vinculados a revisões de código e mudanças compatíveis com patching. A orientação para versionamento de workers descreve execuções fixadas em uma versão de deployment para evitar que uma mudança de código quebre trabalho em andamento. A plataforma não define a semântica da sua tool, mas o princípio é útil: uma execução longa precisa de uma rota conhecida entre a versão que a criou e a versão que a retoma.
Como fazer o rollout sem adivinhar
Um rollout seguro não começa trocando o schema em todos os lugares. Faça a mudança em etapas:
- Inventarie o que está vivo. Conte execuções por versão de contrato, etapa, idade, aprovação pendente e efeito externo já confirmado. Uma fila vazia não prova que não existe um checkpoint adormecido no banco.
- Defina a compatibilidade. Liste campos adicionados, removidos, renomeados, mudanças de enum e alterações de efeito. Para cada item, marque
pin,adapt,migrateoureject. - Implemente o adapter antes do produtor v2. A nova versão deve conseguir ler uma chamada v1 antes de qualquer execução começar a produzir v2.
- Fixe a escolha no início da execução. Uma retomada não deve recalcular o conjunto de tools somente porque o deploy atual é mais novo.
- Envie novas execuções para v2. O produtor, o runtime e a tool devem concordar sobre a versão. Se a seleção ocorrer no prompt, registre também a decisão no envelope do runtime.
- Observe a transição. Registre contagem por versão, rejeições, adaptações, falhas de validação, efeitos duplicados e execuções que ficaram paradas.
- Drene e retire. Remova o adapter só quando não houver execução viva que dependa dele, ou quando uma migração comprovadamente tiver substituído todas elas.
Se o sistema usa MCP, não trate a notificação de alteração da lista de tools como uma migração de contrato. O protocolo define a descoberta e a chamada das tools, e servidores podem avisar que a lista mudou. O versionamento da relação entre uma execução, um schema e uma política continua sendo responsabilidade da aplicação.
Como testar uma mudança de schema?
Monte uma matriz pequena antes do deploy. Cada linha deve ter uma entrada e o resultado esperado na fronteira:
| Caso | Resultado esperado |
|---|---|
| Chamada v1 válida | Adaptada para o formato interno atual. |
| Chamada v1 com campo obrigatório ausente | Rejeitada sem efeito externo. |
| Chamada v2 válida | Executada pelo handler atual. |
| Versão desconhecida | Rejeitada antes do handler. |
| Resposta v2 para consumidor v1 | Adaptada, mantida no worker antigo ou interrompida explicitamente. |
| Repetição depois de timeout | Consulta o estado da operação antes de criar um novo efeito. |
O teste de contrato deve confirmar mais do que o status HTTP. Verifique a versão recebida, o schema validado, os argumentos normalizados, o handler chamado e a ausência de efeito no caminho rejeitado. O artigo sobre testes de contrato para servidores MCP em TypeScript mostra como transformar a interface de uma tool em uma fronteira verificável.
Para execuções já persistidas, mantenha fixtures de cada versão que ainda pode aparecer. Combine isso com testes de regressão de agentes sem comparar texto exato. A pergunta não é se o modelo repetiu a mesma frase. É se o runtime ainda escolheu uma tool permitida, preservou a intenção registrada e evitou executar um efeito com argumentos ambíguos.
Falhas que parecem compatibilidade, mas não são
Aceitar qualquer objeto JSON. Sintaxe válida não significa contrato válido. Valide campos obrigatórios, tipos, enums e invariantes de negócio antes do handler.
Colocar v2 no nome de cada tool para sempre. create_ticket_v1 e create_ticket_v2 podem ser úteis durante uma janela de transição. Se virarem o catálogo permanente, o modelo recebe opções duplicadas e a equipe ganha mais superfícies para manter.
Migrar o estado sem revisar aprovações. Uma aprovação está ligada ao que a pessoa viu, não apenas ao objeto que o banco consegue converter. Mudança material de parâmetros deve reabrir a decisão.
Remover o adapter no primeiro deploy verde. O deploy prova que o novo código iniciou. Não prova que filas, retries, sessões ou checkpoints antigos desapareceram.
Esconder a versão no prompt. O prompt pode ajudar o modelo a escolher uma tool, mas o runtime precisa validar a versão recebida. A fronteira de segurança não pode depender apenas da instrução que o modelo leu.
Checklist antes de publicar a v2
- A execução registra
agentReleaseetoolContractVersionseparadamente? - O adapter valida o payload antigo antes de transformá-lo?
- O handler atual recebe um formato único e já validado?
- Uma versão desconhecida falha antes de qualquer efeito externo?
- A saída também tem uma política de compatibilidade?
- Há fixtures para toda versão ainda viva?
- Aprovações antigas são reavaliadas quando o significado da ação muda?
- O trace permite encontrar a execução, a versão, a decisão e o resultado?
- O adapter tem um critério de retirada verificável?
Conclusão
Versionar uma tool é controlar uma mudança de contrato, não apenas renomear uma função. Fixe a versão no início da execução, valide e adapte na fronteira, mantenha novas execuções separadas das antigas e rejeite o que você não consegue interpretar com segurança. Esse desenho reduz a chance de um deploy quebrar trabalho já aceito e deixa claro quando a compatibilidade terminou.
Se a mudança também envolve checkpoints, filas ou retomadas, conecte essa decisão ao desenho de execução durável com checkpoints, filas e workflows. A tool não deve ser a única parte versionada de uma execução longa.
Como este artigo foi produzido
Samuel Fajreldines escreveu este artigo a partir do inventário de posts e links do repositório, da inspeção de resultados atuais e da leitura da documentação pública de MCP, OpenAI e Temporal. A matriz pin, adapt, migrate e reject é uma síntese original para explicar a fronteira de compatibilidade. O código é ilustrativo: não foi executado contra um provedor de modelo, um servidor MCP ou uma fila de produção.
Usei o RemoteCode como ferramenta do autor para organizar a pesquisa e revisar a consistência do texto. Ele não executou um agente de produção nem substitui a validação do código no ambiente de quem lê.
Fontes consultadas
- Model Context Protocol, "Tools", consultado em 29 de outubro de 2026.
- OpenAI, "Function calling", consultado em 29 de outubro de 2026.
- Temporal, "Workflow Definition", consultado em 29 de outubro de 2026.
- Temporal, "Worker deployment and performance", consultado em 29 de outubro de 2026.