Duas tool calls podem aparecer no mesmo turno do agente e ainda assim não formarem um lote seguro. Se uma consulta depende da outra, se ambas alteram o mesmo registro ou se a segunda precisa observar o efeito da primeira, Promise.all transforma uma decisão de negócio em uma condição de corrida.
A regra prática é simples: paralelize apenas chamadas independentes e sem estado mutável compartilhado. Coloque uma barreira no runtime para dependências, efeitos e recursos que exigem ordem. A configuração do provedor pode permitir várias chamadas, mas a aplicação continua responsável por decidir se pode executá-las.
Este é um caso mais estreito do que a orquestração de agentes em TypeScript. O foco aqui é um único turno que chega com várias tools, não a divisão do trabalho entre agentes.

A decisão: independente ou dependente?
Antes de executar uma lista de chamadas, classifique as tools pelo que elas leem, pelo que alteram e pelo resultado de que dependem. O modelo pode sugerir a lista, mas não deve ser a única fonte dessa classificação.
| Situação | Política inicial | Motivo |
|---|---|---|
get_customer e get_inventory leem recursos diferentes |
Paralelizar | Uma não precisa do resultado nem do efeito da outra. |
reserve_stock e create_order alteram o mesmo pedido |
Serializar ou usar transação | A ordem e a atomicidade fazem parte do efeito. |
search_customer precisa fornecer o ID para get_balance |
Serializar | A segunda chamada não tem entrada completa antes da primeira. |
| Duas tools escrevem no mesmo arquivo ou documento | Serializar por recurso | Mesmo com argumentos diferentes, o estado observado pode ser compartilhado. |
send_email e charge_card são efeitos independentes, mas irreversíveis |
Não paralelizar por padrão | Uma falha parcial pode deixar efeitos difíceis de compensar. |
A documentação da Anthropic sobre uso paralelo de tools recomenda chamadas paralelas para operações independentes e mostra como desativá-las quando a ordem é necessária. A documentação do AutoGen faz a mesma distinção: paralelismo ajuda em tools simultâneas, mas deve ser evitado quando há efeitos colaterais ou estado compartilhado.
A flag do provedor não é a política do sistema
Na Responses API da OpenAI, parallel_tool_calls controla se o modelo pode executar chamadas de tools em paralelo. Na Anthropic, disable_parallel_tool_use dentro de tool_choice limita a emissão a uma chamada em determinados modos. Esses controles são úteis, mas atuam na fronteira do provedor.
Eles não sabem que duas tools internas escrevem no mesmo pedido, que uma aprovação cobre apenas a primeira alteração ou que um worker retomou uma etapa depois de um timeout. Mesmo com a flag ligada, o scheduler da aplicação precisa validar a lista recebida. Mesmo com a flag desligada, uma fila ou dois workers podem criar concorrência depois.
Coloque a decisão no registro de tools
Uma forma pequena de evitar que cada handler invente sua própria regra é manter metadados de execução ao lado do schema:
type ToolMeta = {
mode: "read" | "write";
resource: string;
dependsOn?: string[];
};
const tools: Record<string, ToolMeta> = {
get_customer: { mode: "read", resource: "customer" },
get_inventory: { mode: "read", resource: "inventory" },
reserve_stock: { mode: "write", resource: "order" },
create_order: { mode: "write", resource: "order", dependsOn: ["reserve_stock"] },
};
function canShareBatch(aName: string, a: ToolMeta, bName: string, b: ToolMeta) {
return a.mode === "read" &&
b.mode === "read" &&
a.resource !== b.resource &&
!a.dependsOn?.includes(bName) &&
!b.dependsOn?.includes(aName);
}
Esse trecho é ilustrativo e ainda precisa de validação de argumentos, autorização e um grafo de dependências mais completo. O ponto importante é a localização da decisão. O runtime conhece a tool real, o recurso protegido e as arestas de dependência; o prompt conhece apenas a descrição que recebeu.
Na prática, monte lotes independentes e execute cada lote com Promise.all. Quando uma chamada tiver dependência, aguarde o lote anterior, atualize o contexto e só então prepare a próxima. Para escrita, prefira uma transação, uma fila por recurso ou uma chave de idempotência. Paralelismo não substitui controle de concorrência no banco.
Correlacione resultados por ID, nunca por posição
Uma resposta paralela pode terminar em ordem diferente da ordem em que as chamadas foram emitidas. Cada chamada precisa carregar um identificador estável, e cada resultado precisa voltar ligado a esse identificador. A documentação da Anthropic orienta devolver todos os resultados no mesmo turno e usar tool_use_id para relacionar cada resultado à chamada correta.
Não faça isto:
for (const [index, result] of results.entries()) {
run.toolResults[calls[index].name] = result;
}
Se uma chamada falhar, for cancelada ou terminar depois, o índice deixa de ser uma identidade confiável. Prefira:
for (const result of results) {
run.toolResults[result.callId] = {
status: result.ok ? "ok" : "error",
value: result.value,
};
}
Quando uma dependência não puder ser executada, devolva um erro explícito para o agente ou interrompa o turno. Não invente um resultado vazio para preencher a posição. O erro precisa dizer qual chamada ficou bloqueada, qual dependência faltou e se algum efeito já foi confirmado.
Como testar sem depender de timing acidental
Crie uma fixture com duas leituras que esperam uma barreira manual. Libere as duas juntas e confirme que o scheduler iniciou ambas. Depois use duas escritas do mesmo recurso, libere a primeira antes da segunda e confirme que a segunda não começou cedo. Por fim, faça uma chamada dependente falhar e verifique que o efeito seguinte não foi executado.
Os casos mínimos são:
- duas leituras independentes compartilham um lote;
- duas escritas do mesmo recurso são serializadas;
- uma chamada que depende de um ID espera o resultado anterior;
- resultados fora de ordem continuam ligados ao
callIdcorreto; - uma versão desconhecida ou uma dependência ausente falha antes do efeito externo.
Executei uma fixture local determinística com Node.js v25.5.0 em 30 de outubro de 2026. Ela usou promessas controladas, sem provedor de modelo, banco ou rede. O resultado confirmou os cinco comportamentos acima. Isso prova apenas a lógica da fixture, não uma propriedade de latência ou segurança de um SDK específico. Para condições de corrida mais próximas do seu domínio, use testes determinísticos para condições de corrida em JavaScript, além de testes no banco e na fila que realmente protegem o efeito.
Falhas comuns
Usar Promise.all para toda lista. A API do modelo não conhece as invariantes do seu domínio. Separe leitura independente de mutação antes de criar as promessas.
Confiar apenas na instrução “não faça chamadas paralelas”. Isso reduz uma possibilidade do modelo, mas não protege dois workers, retries, callbacks ou reprocessamentos da fila.
Serializar tudo e chamar isso de solução. É seguro como diagnóstico, mas pode aumentar a latência e esconder quais leituras seriam independentes. Classifique as tools e reabra o paralelismo apenas com teste.
Correlacionar pelo nome. Duas chamadas da mesma tool no mesmo turno precisam de IDs diferentes. O nome identifica a capacidade, não a ocorrência.
Ignorar falha parcial. Se uma leitura terminou e uma escrita falhou, registre o estado conhecido. Não repita automaticamente um efeito só porque o lote não terminou.
Checklist antes de liberar chamadas paralelas
- Cada tool declara se lê ou altera estado?
- O runtime conhece o recurso compartilhado e as dependências?
- O scheduler decide o lote, em vez de aceitar
Promise.allindiscriminado? - Cada resultado volta com o ID da chamada original?
- Efeitos externos têm idempotência, transação ou fila por recurso?
- Há teste negativo para dependência ausente e falha parcial?
- O trace registra o lote, a ordem, os bloqueios e os efeitos confirmados?
- A configuração do provedor foi tratada como otimização de emissão, não como autorização de negócio?
Conclusão
Parallel tool calls são uma otimização útil quando a independência é uma propriedade demonstrável. Não são um atalho para executar qualquer lista mais rápido. Mantenha a política no runtime: classifique as tools, paralelize somente leituras independentes, serialize dependências e estado compartilhado, e relacione cada resposta por ID.
Se o lote faz parte de um fluxo maior, combine essa política com a validação de tool calls antes do handler e com a prática de verificar o efeito antes de repetir. Assim, a decisão de concorrência fica ligada à validação e à recuperação, em vez de virar uma flag isolada.
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 da OpenAI, Anthropic e AutoGen. A matriz de independência, dependência e estado compartilhado é uma síntese original. A fixture é ilustrativa e foi executada localmente com Node.js, sem chamar um provedor de modelo ou uma ferramenta 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
- OpenAI, “Responses API create reference”, consultado em 30 de outubro de 2026.
- Anthropic, “Uso paralelo de tools”, consultado em 30 de outubro de 2026.
- Microsoft AutoGen, “Agents”, consultado em 30 de outubro de 2026.