Saltar para o conteúdo

Este artigo aborda os limites do cliente e da plataforma subjacentes à experiência do utilizador e às interações com o Ask DoorDash: como passámos dos carrosséis de hackathons para esse modelo, como um único artefacto serve três leitores e como o contexto e o estado se mantêm consistentes à medida que os consumidores navegam pelo DoorDash. Segue-se «Construir o DoorDash Assistant: Uma Visão Geral da Engenharia» e às nossas análises aprofundadas A criação do Ask DoorDash (Parte 2): Inteligência, A criação do Ask DoorDash (Parte 3): Avaliação, e A criação do Ask DoorDash (Parte 4): Uma plataforma para a criação e evolução de agentes.


O Ask DoorDash transforma pedidos como «prepara-me um jantar de 60 dólares para 10 pessoas», «ajuda-me a fazer tacos de frango» e «petiscos para a semana» em listas de compras editáveis. Gera uma lista com produtos disponíveis para compra, preços, fotografias, controlo de quantidades e botões simples para substituir qualquer artigo e concluir a compra. No caso de pedidos de restaurantes, são apresentados estabelecimentos e pratos que se integram no fluxo de compras nativo. Ambas as funcionalidades funcionam no mesmo cliente e na mesma infraestrutura da plataforma. Este artigo centra-se no caso de utilização de compras de supermercado, uma vez que este é o que coloca maior pressão sobre todas as principais complexidades da criação de uma interface intuitiva para compras orientadas por um agente. As listas de compras de supermercado contêm normalmente dez ou vinte itens, passam por várias rondas de revisão e têm de lidar facilmente com restrições do consumidor, como orçamentos e restrições alimentares.

As sessões de utilizadores de julho de 2026 revelaram que, nas sessões relacionadas com compras de mercearia em que era apresentada uma lista de compras, os consumidores realizaram, em média, cerca de duas interações com a interface do utilizador cada um. Cerca de um terço dessas sessões culminou na aplicação da lista a um carrinho de compras. O assistente capta a intenção geral dos consumidores e sintetiza-a. O resultado são componentes nativos, que permitem um refinamento granular por parte dos consumidores. As edições simples atualizam imediatamente um artefacto de referência, enquanto as alterações que exigem raciocínio dão início a uma nova ronda do agente.

Para criar uma experiência de compras colaborativa e eficaz entre o consumidor e o agente, os agentes de compras têm de se integrar numa interface interativa baseada em dados de comércio em tempo real e no contexto atual da aplicação do consumidor. Quando um consumidor está a fazer compras numa aplicação, espera ver, à primeira vista, artigos reais que possa adquirir. O texto pode descrever esse ambiente, mas os componentes interativos baseados em imagens permitem que os consumidores o percorram efetivamente. O contexto também é importante. Os consumidores que abrem a DoorDash têm expectativas diferentes das que têm em relação a uma aplicação de chat de uso geral. Fazem compras navegando, passando de lojas e páginas, em vez de descreverem o que querem. Ir ao encontro deles onde se encontram é uma peça essencial do quebra-cabeças.

As grandes coisas têm um início modesto

O assistente de compras da DoorDash começou por ser um projeto de hackathon. Sabíamos desde o início que a integração dos componentes existentes da DoorDash seria um elemento fundamental de tudo o que viéssemos a construir. As primeiras versões baseavam-se fortemente em carrosséis: primeiro, uma lista de lojas próximas em potencial; depois, um carrossel de resultados de pesquisa para cada artigo que o agente procurava. Eram apresentados em linha, cada um com o respetivo termo de pesquisa associado. O resultado era, essencialmente, um registo de conversa com resultados comerciais colados nele.

Em termos de funcionalidade, era semelhante ao que lançamos hoje. No que diz respeito à experiência do utilizador, era mais rudimentar, e vale a pena analisar os pormenores.

Depois de encontrar um conjunto de lojas, o assistente perguntou qual delas explorar e aguardou que a resposta fosse digitada. Cada carrossel representava um único artigo de que o consumidor precisava, e escolher uma opção adicionava esse artigo ao carrinho. Cada um aparecia com as opções dispostas em toda a largura do ecrã e a escolha ainda em aberto. Assim, organizar as compras para uma semana significava percorrer a lista, uma seleção de cada vez, sem possibilidade de selecionar um conjunto de artigos de uma só vez.

Por trás de tudo isto estava uma falha que se manifestava em ambos os sentidos: a interface tinha sido concebida principalmente para apresentar o trabalho do assistente. Cada carrossel era encabeçado pela consulta que o tinha gerado e os resultados apareciam na ordem em que tinham sido recuperados. Os termos de pesquisa e os conjuntos de resultados em bruto são o material de trabalho de um agente. Apresentar os resultados diretamente é o equivalente, na interface, a imprimir um rastreio da pilha. O assistente submetia-se com a mesma facilidade à interface, trabalhando um item de cada vez, porque uma pilha de carrosséis por item era o que o ecrã conseguia mostrar. Cada um tinha sido concebido em função das limitações do outro, em vez de ser concebido em função do utilizador.

A lição não foi que reutilizar componentes existentes fosse errado; a familiaridade que estes transmitem é precisamente a razão pela qual o assistente se assemelha ao DoorDash. Simplesmente, tínhamos criado o contentor errado para eles. É o contentor que determina a ordem em que aparecem, ao lado de que outros elementos se situam e o que um consumidor pode fazer com eles. Acima de tudo, um utilizador precisa de uma superfície de compras, em vez de uma interface de conversação. Tínhamos criado uma interface de conversação que, por acaso, continha componentes de compras. A reutilização de componentes deu-nos o vocabulário. Compô-lo numa superfície foi a parte mais difícil.

O que sobreviveu ao hackathon foi a base: a mesma infraestrutura de streaming que foi criada é aquela em que a experiência funciona atualmente. Entretanto, a camada de descodificação que transformava os resultados das ferramentas na interface do utilizador não perdurou. O assistente já não apresenta, de todo, os resultados das ferramentas. Em vez disso, investimos tempo na construção de uma arquitetura de artefactos que separa os requisitos dos utilizadores dos requisitos de armazenamento e dos agentes. Por outras palavras: deixámos de descodificar o material de trabalho do agente para o apresentar e começámos a construir em torno das necessidades dos diferentes componentes e utilizadores do sistema.

Figura 1: O mesmo pedido de compras, com um ano de diferença. A versão criada no hackathon apresentava um carrossel por artigo, cada um encabeçado pela consulta que o gerou; a seleção ficava a cargo do consumidor. Atualmente, o assistente apresenta cada lista como um cartão na conversa, com os artigos mais populares já selecionados.

Um objeto, três lentes

Uma lista de compras tem três leitores: o serviço que a armazena, o agente que a revê e a pessoa que faz as compras. Cada um precisa de ver algo diferente. Manter essas informações separadas é o que permite que a edição feita pelo consumidor apareça imediatamente e se mantenha válida sem exigir mais uma iteração do modelo.

O formulário guardado é o sistema de registo: um blob JSON armazenado como um artefacto no Managed Agent Services, conforme descrito na publicação da plataforma. Tem de estar completo e é a única cópia oficial.

O agente não lê esse blob diretamente. Uma lista de compras interativa contém muito mais detalhes do que um modelo necessita para a maioria das respostas. Repetir o nome de cada produto, preço, quantidade, substituição e cada elemento de metadados de apresentação a cada turno entraria em conflito com o que o consumidor realmente pediu. Assim, quando um agente emite um widget, o que permanece no contexto da conversa é um resumo compacto: a loja e o que a lista contém. Quando uma resposta requer o estado exato, o agente lê o artefacto e obtém uma visão reduzida, sem os metadados de exibição. Essa visão é inequívoca e suficientemente pequena para ser analisada de forma eficiente. Uma lista média ocupa entre 40 e 60 KB de JSON. O que permanece no contexto da conversa é uma única linha de cerca de 240 caracteres, aproximadamente 250 vezes menor.

O que o consumidor vê é diferente, e assume várias formas. Na conversa, a lista é apresentada como um widget compacto: a loja, o nome da lista, o tempo estimado de entrega, algumas fotos dos artigos e um botão «Adicionar ao carrinho» com o total estimado. Isso é suficiente para avaliar a lista num relance, sem sair do chat. Um botão «Ver lista» abre a visualização em página inteira, que apresenta a lista completa e tudo o que é necessário para a analisar em pormenor. Ambas as visualizações foram concebidas para serem compreendidas imediatamente e para que se possa agir diretamente a partir delas.

Um pormenor mostra até que ponto estas perspetivas divergem. Quando o agente escolhe um produto para um item da lista, mantém os outros produtos que corresponderam à mesma pesquisa, e o consumidor pode alternar entre eles com um toque. Mantém dez candidatos por item, e 99,7% dos itens têm correspondências suficientes para preencher esse número, pelo que quase todas as linhas na perspetiva do consumidor têm uma seleção e nove alternativas por trás dela. A visualização do consumidor apresenta todas essas alternativas. A visualização do agente não apresenta nenhuma delas, porque a escolha entre elas é da responsabilidade do consumidor (a menos que seja explicitamente pedido ao assistente para o fazer).

Figura 2: Uma lista, três perspetivas. O consumidor vê preços, fotografias e uma opção de troca por trás de cada linha. O agente vê apenas a loja e o que a lista contém. O artefacto contém tudo isso, incluindo nove alternativas só para as folhas de lasanha.

Manter os pés no chão

Com um carrinho que contém iogurte de framboesa, ao clicar em «Recomendar combinações para o carrinho» são apresentadas duas sugestões: granola, «porque combina com o seu iogurte», e mirtilos, «para adicionar fruta ao seu iogurte» (ver Figura 3). O caso de contraste já existe no nosso próprio produto, no mesmo ecrã: a página do carrinho apresenta um carrossel de artigos complementares que sugere artigos que o consumidor possa querer adicionar, sem qualquer justificação. O nosso agente do carrinho poderia ter apresentado exatamente isso: uma lista de itens plausíveis. Os itens poderiam muito bem ter sido os mesmos em qualquer dos casos. É a justificação que indica ao consumidor que a sugestão foi fundamentada e não apenas plausível e, uma vez que cada widget é também um local para agir, essa distinção tem um impacto real.

É isso que a integração com a realidade oferece: a diferença entre uma experiência de compras cativante e uma mera novidade. Se o resultado apresentado pelo assistente não for tangível, os consumidores têm poucos motivos para abandonar a rica experiência que a aplicação DoorDash já proporciona. O texto continua a desempenhar o seu papel, mas de forma secundária. Colocámos deliberadamente a lógica do assistente dentro dos widgets e à sua volta, em vez de mostrar as células dos itens exatamente como aparecem noutras partes da aplicação.

A integração também tem de ser assegurada ao nível dos dados, e aí o trabalho é dividido. Um agente escolhe um tipo de conteúdo suportado e fornece os dados comerciais correspondentes; o cliente iOS é responsável pela apresentação e interação, mapeando esse conteúdo para componentes nativos do sistema de design da DoorDash. Um pedido de mercearia gera uma lista de compras editável com quantidades, substituições e um subtotal. Um pedido de restaurante pode gerar fichas de loja, fichas de artigos ou sugestões para o carrinho.

É essa separação que fundamenta os próprios dados. Os valores comerciais num widget não provêm do modelo. Os preços, o inventário, o horário de funcionamento da loja e o conteúdo do carrinho são lidos a partir dos mesmos sistemas de registo que o resto da aplicação utiliza, pelo que um preço apresentado no Ask corresponde ao preço na página da loja naquele momento. O conteúdo tipado também proporciona ao backend e ao cliente um contrato estável. A implementação de um novo widget requer mais do que uma simples alteração no prompt. O agente tem de saber quando o utilizar e o que nele colocar, e o cliente precisa de um esquema nomeado, além do código para o descodificar e apresentar. Para além disso, um novo widget chega ao consumidor através do mesmo mecanismo de conversação e transmissão que qualquer outro.

Figura 3: Dois tipos de sugestões relativas ao mesmo carrinho. Apenas uma delas explica o motivo.

Widgets como canais de E/S

Cada novo widget que integramos contribuiu para tornar o assistente mais funcional. A razão para isso tem tanto a ver com a entrada de dados como com a saída de resultados. Existe um limite que rege todos eles: uma edição determinística permanece no cliente e aplica-se diretamente ao artefacto, enquanto uma alteração que exija uma avaliação dá início a um ciclo do agente. Os exemplos abaixo estão organizados em função de onde essa linha se situa.

A versão do hackathon perguntava qual a loja que o consumidor pretendia e, em seguida, aguardava que este a escrevesse. Não tinha forma de escolher por si própria; hoje em dia, essa escolha é automática. Muitas outras perguntas ainda dependem do consumidor, e a introdução direta de texto pode ser uma forma pouco eficaz de recolher as respostas. A introdução de texto é uma limitação de fluxo único: o consumidor pode saber todas as respostas, mas só as consegue fornecer à velocidade a que consegue escrever ou falar.

É aqui que os widgets ganham o seu lugar. Um consumidor pode ter dez operações para aplicar a uma lista de compras, sendo cada uma delas simples. Individualmente, são triviais; quando descritas em prosa, tornam-se uma tarefa complexa. É a diferença entre olhar por cima do ombro de alguém e dizer-lhe quais os botões que deve premir, e premir os botões por si próprio.

As perguntas esclarecedoras estruturadas são o exemplo mais claro, e o padrão é inspirado nos agentes de codificação: a ferramenta AskUserQuestion da Claude Code desempenha a mesma função para os programadores. Disponibilizamos um widget que interrompe o consumidor com um pequeno conjunto de perguntas (de escolha múltipla e seleção múltipla), permitindo-lhe orientar a conversa em momentos genuinamente abertos. Tornou-se um pilar fundamental do modelo de interação no agente de mercearia e está agora a ser adotado no agente de restaurante para simplificar o mesmo tipo de interação. Integramos esta funcionalidade com a nossa plataforma de memória do consumidor, o que reduz significativamente as opções, mas continuam a existir lacunas de conhecimento que exigem a intervenção do consumidor. Fazer perguntas é inevitável. Mas podemos fazê-lo de uma forma que seja fácil e eficiente para o consumidor responder.

A visualização da lista em página inteira é onde a colaboração de entrada/saída se concretiza. Um consumidor pode alterar a quantidade de um artigo, eliminar um artigo, trocar um artigo por uma das alternativas que o assistente encontrou ao lado dele e adicionar a lista finalizada ao seu carrinho. Cada uma dessas ações manipula o artefacto diretamente. Nenhuma delas requer que o assistente atue em nome do consumidor, e nenhuma implica uma ida e volta ao LLM. O caso da troca aproveita melhor os conjuntos de candidatos da versão desenvolvida no hackathon. As alternativas entre as quais o consumidor escolhe são os outros candidatos que o assistente recuperou quando selecionou o artigo. A lista de todas as opções disponíveis para o consumidor preenchia anteriormente um carrossel. Agora, encontram-se a um toque de distância de uma decisão, em vez de se apresentarem diante do consumidor como uma questão em aberto.

A mudança de loja é a exceção, e a exceção é a parte interessante. Não se trata de uma alteração da lista existente. Transforma-se num pedido ao assistente que reconstrói a lista na loja alternativa. Esta interação também é rara: foi a primeira ação em 0,1% das sessões ativas, o que corresponde aproximadamente ao que esperaríamos se o limite fosse definido no ponto certo. Se essa linha for traçada no local errado, o consumidor ou espera que o assistente faça algo que poderia ter feito com um simples toque, ou tem de percorrer várias etapas com toques que poderiam ter beneficiado do discernimento do assistente.

O artefacto torna a divisão segura, e os dois lados escrevem nele de forma diferente. Uma alteração na quantidade, uma remoção ou uma troca segue o caminho mais curto: o cliente atualiza a lista no local e a alteração aparece imediatamente. Uma alteração de armazenamento, por outro lado, dá início a um ciclo do agente, porque é necessário recriar a correspondência de toda a lista com um catálogo diferente. Uma revisão do agente não sobrescreve o que lá estava; produz uma nova lista derivada da anterior.

O resultado é que uma edição direta tem carácter vinculativo. Antes de uma intervenção alterar a lista, o agente consulta a versão atual em vez de se basear na transcrição; assim, se o consumidor retirar o leite de aveia, a intervenção seguinte começa com uma lista que já não inclui o leite de aveia.

O comportamento observado durante o mesmo período mostra onde se situa essa linha. Imediatamente após o primeiro pedido, quase três quartos das primeiras ações seguintes ocorreram através de um componente. A partir da segunda mensagem, a situação inverte-se, e as respostas digitadas passam a constituir a maioria em todos os níveis de profundidade que medimos. Os controlos absorvem os ajustes imediatos e estruturados; a linguagem assume o controlo quando a alteração é mais significativa ou mais difícil de expressar com um toque.

Figura 4: A lista ao atravessar o limite de uma volta. O consumidor retira as maçãs diretamente na visualização da lista, sem qualquer ida e volta pelo modelo. Quando a mudança de loja dá início a uma nova volta, o agente lê o artefacto em vez da transcrição, pelo que a lista que reconstrói na nova loja continua sem essas maçãs.

Todas as revisões do assistente permanecem ativas

Quando o assistente revê uma lista de compras, a versão que substitui permanece no ecrã e continua a poder ser utilizada.

Essa não foi a nossa primeira implementação. A primeira versão ocultava as listas substituídas: assim que uma revisão era publicada, a lista subjacente deixava de poder ser visualizada ou editada. Uma lista atual, sem confusão. Parecia um comportamento mais organizado. No entanto, o que isso fez foi tornar a revisão algo dispendioso. Se solicitar uma alteração pudesse apagar uma lista com a qual se estava satisfeito, a atitude mais segura seria deixar de solicitar alterações, o que compromete todo o sentido de um fluxo colaborativo.

Assim, cada revisão auxiliar produz agora um novo artefacto derivado do anterior, e cada um permanece no local onde foi criado na conversa. Se recuar na página, as versões anteriores continuam todas lá, ainda ativas: um consumidor pode abrir uma lista anterior, editá-la ou adicioná-la ao seu carrinho em vez da versão mais recente. Pedir uma alteração não custa nada, porque o que tinham há um momento está à distância de um simples deslocamento para trás.

Nem todas as listas criadas pelo assistente se tornam uma versão que o consumidor vê. Quando o assistente elabora uma lista de sugestões e conclui que a loja não dispõe do que foi solicitado, essa lista nunca é apresentada. O consumidor é informado, por meio de texto, de que faltavam artigos na loja e que uma nova lista está a caminho. É um dos poucos casos em que a prosa desempenha o papel de forma direta e que ilustra a razão subjacente a tudo o resto neste artigo: a interação é utilizada para simplificar e o texto é utilizado para justificar.

Quando uma lista se transforma num carrinho

Um assistente que edita diretamente o carrinho pode remover um artigo que o consumidor pretendia comprar ou adicionar um que este nunca confirmou, sem que lhe seja pedido. Basta um único erro para perder a confiança do consumidor, porque a falha ocorre de forma silenciosa e afeta precisamente o que este está prestes a pagar.

Por isso, apresentamos, em vez disso, uma lista de compras como proposta. Esta pode conter dezenas de artigos e passar por várias rondas de revisão, quer isso signifique remover um produto da despensa que o consumidor já tenha, alterar uma quantidade, trocar um produto ou reformular parte de uma receita, e nada disso afeta o carrinho. Para confirmar a compra, é necessário clicar explicitamente em «Adicionar ao carrinho», que é a estrutura mais simples que encontrámos e que dá ao consumidor controlo real sem o obrigar a supervisionar cada passo.

Os dois elementos ainda têm de ser conciliados, e a escolha interessante é quem o faz. O assistente analisa o carrinho enquanto elabora a lista, mas não a ajusta automaticamente para que corresponda: se uma receita precisar de duas bananas, a lista pede duas, independentemente de já haver ou não quatro no carrinho. Elaborar a lista com exatamente o que foi solicitado e conciliá-la com o carrinho numa etapa separada permite manter cada decisão num único local.

Essa etapa surge no momento da confirmação. Quando a lista e o carrinho coincidem, são apresentadas ao consumidor as duplicatas e este decide se pretende substituir essas quantidades ou aumentá-las. É um momento breve, mas são momentos breves como este que determinam se se ganha ou se perde a confiança.

Transmitir o contexto em toda a plataforma DoorDash

Não basta que o assistente esteja disponível num único local; tem de ser acessível a partir de qualquer ponto da aplicação. Abrir o assistente já é um forte sinal de intenção, mas um sinal ambíguo. Um consumidor, nesse momento, pode estar a fazer uma encomenda num restaurante favorito, a comprar mantimentos para a semana, a procurar uns auscultadores novos ou a procurar apoio relativamente a uma encomenda anterior. Um consumidor que tenha acedido a uma determinada mercearia está a enviar um sinal muito mais claro: é provável que pretenda encher o carrinho nessa loja. Transmitir esse contexto ao assistente é o que lhe permite concentrar-se na intenção provável, em vez de ter de perguntar.

O sistema de âmbito que concebemos para este projeto abrange o cliente e o Gateway. O cliente associa elementos-chave do contexto de entrada a cada pedido: o tópico sob o qual o consumidor entrou (mercearia, restaurante), a loja que está a consultar e alguns identificadores relacionados. O Gateway utiliza o tópico para decidir a que agente encaminhar o pedido e os restantes elementos para fornecer às ferramentas os identificadores de que necessitam.

O Scope é, deliberadamente, um conjunto simples de pares chave-valor, em vez de um esquema tipado, e é o cliente que decide o que nele é incluído. As chaves não reconhecidas são transmitidas em vez de rejeitadas, pelo que uma nova superfície pode começar a enviar contexto sem ter de aguardar uma alteração no Gateway. A exceção é o próprio tópico: adicionar um novo implica indicar ao Gateway para que agente deve encaminhá-lo. Assim, embora uma nova superfície seja de baixo custo, um novo domínio não o é.

O tema é também a parte que se aplica apenas à primeira mensagem de um contexto. Isso dá ao assistente uma base sólida a partir da qual trabalhar, sem ser excessivamente prescritivo, e é o que permite aos utilizadores sair de um âmbito quando optam explicitamente por fazê-lo. Um utilizador que utilize o assistente dentro de um restaurante pode sair completamente do âmbito do restaurante, pedindo-lhe para «encontrar uma receita para este prato e preparar-me uma lista de compras, para que eu o possa fazer em casa». Esse pedido não é gratuito, mas o custo é um pequeno desvio no percurso, em vez de um beco sem saída: o agente do restaurante tem de reconhecer o pedido como estando fora do seu domínio e devolvê-lo antes que outro agente o receba.

Em certos casos, os âmbitos também nos permitem ignorar completamente o modelo. Tomemos como exemplo o ponto de entrada «Reordenar o meu último carrinho». Poderia simplesmente enviar essa frase ao assistente como uma mensagem e, embora isso funcionasse, anexar um âmbito permite-nos ser mais específicos. O caso «reordenar» é, na prática, determinístico, pelo que podemos contornar o LLM e devolver uma lista de compras quase imediatamente. O que é devolvido é um widget comum de lista de compras: o consumidor pode alterar quantidades, trocar produtos ou pedir ao assistente para a rever no chat, exatamente como faria com qualquer outra lista. Note-se que esta omissão só se aplica se a loja ainda tiver todos os artigos do carrinho anterior; se um artigo não estiver disponível, o pedido é encaminhado para o agente de mercearia, que reconstrói a lista da mesma forma que faria a partir de um pedido digitado.

O escopo é também o que torna possível uma conversa única e contínua ao longo de uma sessão da aplicação, mas só funciona porque o escopo, a associação de agentes e o estado da sessão são mantidos separados. Tratamos o âmbito como um contexto por turno. A fixação, descrita anteriormente nesta série de publicações do blogue, mantém os turnos de acompanhamento com o agente que resolveu o turno anterior. Tanto a fixação como a transcrição (a conversa propriamente dita) são associadas ao chat e não ao âmbito; assim, um consumidor que se desloca de uma loja para outra redireciona a fixação para um agente de um novo domínio sem descartar o que foi dito. Vários agentes especializam-se internamente, e o sistema continua a comportar-se como um único assistente.

Figura 5: Uma conversa, iniciada a partir de dois locais. O âmbito acompanha cada pedido, pelo que fazer um pedido dentro do Safeway faz com que a lista seja criada no Safeway.

Conclusão

A integração de agentes em produtos de consumo ainda está numa fase inicial. No entanto, as bases estão a tornar-se claras. Os widgets são onde os consumidores realizam o trabalho e a interação entre o consumidor e o agente é o que os orienta. Existem atualmente três «leitores» do nosso artefacto de lista de compras, cada um dos quais necessita de uma perspetiva diferente do mesmo. Utilizamos a parte voltada para o utilizador para ancorar o consumidor em produtos reais e para tornar as modificações comuns ações com um único toque. À medida que os agentes assumem mais tarefas, esperamos que o que um consumidor precisa de ver e o que um agente precisa de analisar continuem a mudar, e que a linha divisória entre uma interface de aplicação e uma interface de chat se torne mais difícil de traçar.

A plataforma de experimentação da DoorDash gere mais de 60 000 «feature flags» em cerca de 623 repositórios. Identificámos mais de mil «feature flags» obsoletas; por outras palavras, a funcionalidade que outrora controlavam já foi totalmente implementada, mas a «flag» e a sua lógica de ramificação continuam na base de código como peso morto. Limpar apenas uma manualmente demora a um engenheiro entre uma a duas horas. Com cerca de 2 300 novas «flags» a serem criadas todos os meses, o atraso só aumenta.

Assim, criámos um sistema LLM multiagente que elimina as marcações obsoletas de forma integral, desde um ticket do Jira até um pedido de integração (PR) pronto para fusão, sem qualquer intervenção humana, exceto por um engenheiro que confirma o valor alvo e a implementação do PR. Das 50 marcações obsoletas mais recentes que processou, o sistema produziu PR utilizáveis para 45 delas, com um custo médio de 4,79 dólares e 13,8 minutos por marcação, poupando inúmeras horas de trabalho de engenharia. O sistema que criámos não só tinha de ser eficaz, como o seu limite de fiabilidade tinha de ser a exaustividade, e não a correção; se o agente falhasse, tinha de o fazer de forma segura, sem introduzir quaisquer alterações no código que causassem falhas.

Este artigo aborda as razões pelas quais as ferramentas prontas a usar não conseguiram alcançar este objetivo na nossa base de código, a forma como concebemos o sistema e o que aprendemos durante a sua implementação. Nota: Este trabalho foi submetido a revisão por pares e aceite para a secção dedicada à indústria da ICSME 2026.

Por que vale a pena automatizar a remoção de «feature flags» obsoletas

Os «feature flags» são um pilar fundamental da entrega moderna de software. Permitem-nos implementar funcionalidades de forma gradual, realizar experiências A/B e ativar mecanismos de desativação imediata quando algo corre mal. No entanto, cada «flag» tem um ciclo de vida e, assim que uma funcionalidade é totalmente implementada, o respetivo «flag» torna-se obsoleto. Os «feature flags» obsoletos são frequentemente deixados como dívida técnica, uma vez que o tempo de um engenheiro tem mais valor na criação de novas funcionalidades do que na sua limpeza. 

A sua lógica condicional já não tem qualquer utilidade, mas não fica simplesmente por aí sem causar danos:

  • Os «flags» obsoletos podem permanecer no código-fonte durante anos. Estudos anteriores revelaram que cerca de 25 % dos «feature flags» persistem durante mais de oito anos em projetos de código aberto; os profissionais referem frequentemente que a limpeza é um processo moroso e que é repetidamente adiada.
  • Um sinalizador obsoleto é uma dívida técnica. Cada sinalizador obsoleto acrescenta um ramo, um caso de exceção e um pouco mais para ler e analisar, o que pode ser especialmente complicado durante um incidente.
  • Os indicadores obsoletos podem constituir um risco operacional. Um indicador obsoleto mantém ativo um caminho de código inativo que pode ser reativado acidentalmente. Num caso bem documentado, uma empresa de negociação reutilizou um indicador obsoleto durante uma implementação, o que provocou transações não intencionais no valor de 460 milhões de dólares em apenas 45 minutos.

Se multiplicarmos dezenas de milhares de sinalizadores por uma taxa de criação mensal de quatro dígitos, torna-se evidente que a limpeza manual simplesmente não consegue acompanhar o ritmo. Eliminar manualmente o nosso acúmulo de tarefas em atraso custaria milhares de horas de engenharia que seriam mais bem empregues no desenvolvimento.

Valores dinâmicos

Os «feature flags» na DoorDash são designados por «valores dinâmicos» (DVs). São geridos pela nossa plataforma de experimentação. Ao longo deste artigo, o termo «DV» é sinónimo de «feature flag».

O ciclo de vida de um DV segue o padrão criação → experimentação → implementação → obsoleto → remoção. Classificamos um DV como obsoleto quando este cumpre todos os seguintes critérios:

  1. Não sofreu alterações nos últimos 90 dias.
  2. Ainda é referenciado no código.
  3. Ainda não se encontra em estado de fim de vida útil (arquivado ou retirado de serviço).
  4. Não consta de nenhuma lista de exclusão.

Uma tarefa cron diária cria um ticket STALEDV no Jira para cada DV obsoleto identificado e atribui-o ao criador do DV. Isto marca o início da fila a partir da qual o nosso sistema de limpeza opera.

Fluxo de trabalho baseado em agentes vs. ferramentas de limpeza baseadas em AST

A abordagem automatizada mais conhecida para a limpeza de flags é o Piranha e o seu sucessor poliglota, que simplificam as condições condicionadas por flags através da correspondência de padrões na árvore de sintaxe abstrata (AST). Funciona bem quando uma flag é lida através de uma chamada direta à API que uma regra de reescrita consiga corresponder a um padrão.

Mas não é assim que se acede aos DVs na DoorDash. Os nossos serviços utilizam um padrão de wrapper de três camadas com injeção de dependências:

// Layer 1: RuntimeKeys.kt – the DV name as a string constant
object RuntimeKeys {
  const val ENABLE_X = “enable_feature_x”
}

// Layer 2: FeatureFlags.kt – a DI wrapper that calls the DV client
@Singleton
class FeatureFlags @Inject constructor(
    private val dvClient: DynamicValuesClient
) {
  fun shouldEnableX(userId: String): Boolean =
    dvClient.getBoolean(ENABLE_X, withContext(“user_id” to userId), false)
}

// Layer 3: business logic – uses the flag via dependency injection
class MyService @Inject constructor(
    private val featureFlags: FeatureFlags
) {
  fun process(userId: String) {
    if (featureFlags.shouldEnableX(userId)) handleNewPath()
    else handleOldPath()
  }
}

Trata-se de uma separação clara de responsabilidades, mas isso significa que o nome DV, a chamada à API e a lógica condicional se encontram, cada um, num ficheiro diferente, ligados apenas através de chamadas a métodos e injeção de dependências. Trata-se de relações semânticas, não sintáticas; os comparadores de padrões AST não conseguem segui-las. 

Determinação do valor-alvo

Existe um problema adicional que uma ferramenta que trabalha apenas com código-fonte não consegue resolver: a determinação do valor-alvo e a consulta ao estado da implementação em tempo real. Uma ferramenta que apenas lê o código-fonte poderia, sem perceber, codificar o valor errado e alterar silenciosamente o comportamento. Garantir que a ferramenta efetue a limpeza correta do código requer conhecer o estado da implementação em tempo real do sinalizador, que se encontra na plataforma de experimentação, e não no repositório. Por exemplo, a plataforma de experimentação da DoorDash suporta a utilização de flags de funcionalidade do tipo inteiro, cadeia de caracteres e JSON. Uma leitura em tempo real do valor-alvo garante que a ferramenta está a modificar o código com o valor correto da flag de funcionalidade, que representa o estado atual do sistema. Da mesma forma, as informações sobre a implementação também são uma parte importante da equação. Uma experiência pode ter sido implementada em 60 % e, posteriormente, abandonada. A ferramenta teria de ter a inteligência para determinar que se trata de uma implementação parcial e solicitar a confirmação do utilizador antes de efetuar qualquer limpeza. Isto pode levar o utilizador a concluir a implementação/reversão antes de utilizar a ferramenta para efetuar a limpeza, ou levá-lo a ignorar a limpeza por enquanto.

Dimensão e complexidade

Por fim, mesmo um DV booleano simples pode afetar entre cinco a 20 ficheiros, depois de ter em conta a constante, o wrapper, todos os locais de chamada e a atualização ou eliminação de testes relevantes. Esta é uma tarefa que requer compreensão do código, não apenas a correspondência da sintaxe. Foi exatamente por isso que optámos por utilizar agentes LLM. Mas a tarefa não se resumiu a escrever um prompt e pedir ao agente para realizar a limpeza; isso não permitiria uma limpeza eficiente em termos de custos e tempo, com mecanismos de proteção em grande escala.

Um fluxo de trabalho em duas fases, com intervenção humana

O sistema foi desenvolvido com base no Agent Development Kit (ADK) da Google e funciona em duas fases, com um único ponto de verificação humano entre elas, conforme ilustrado na Figura 1.

Figura 1: Arquitetura do sistema. Um agente orquestrador executa a análise da Fase 1, extrai DVs desatualizados do Jira, consulta a plataforma de experimentação sobre o estado da implementação através do Protocolo de Contexto do Modelo (MCP) e pesquisa a base de código; um engenheiro verifica o relatório. A Fase 2, por sua vez, gera agentes de limpeza paralelos em árvores de trabalho Git isoladas, que editam, testam, verificam a cobertura e executam a verificação de conformidade antes de abrir um PR.

Fase 1: Análise e relatório

Um agente orquestrador, neste caso Claude Sonnet, processa os pedidos de DV desatualizados. Para cada DV, ele:

  1. Recupera os tickets DV do Jira desatualizados através da ferramenta de linha de comandos da Atlassian.
  2. Consulta a plataforma de experimentação da DoorDash através do Protocolo de Contexto do Modelo (MCP) para obter os metadados do DV — o seu UUID, a percentagem de implementação e o valor-alvo.
  3. Identifica o repositório local e procura todas as referências de código ao DV.
  4. Gera um relatório estruturado que propõe um valor-alvo e enumera os ficheiros afetados para cada DV.

Essa integração com o MCP é o herói discreto do projeto. Ao ler o estado atual da implementação em produção, o agente determina o valor-alvo correto, em vez de ter de adivinhar com base no código. Por exemplo, um DV que foi introduzido para uma funcionalidade que acabou por ser abandonada apresenta uma implementação de 0% na plataforma; assim, o agente sabe que deve removê-lo utilizando o valor de referência, em vez de partir do princípio de que a funcionalidade está ativa.

Ponto de controlo humano

Antes de qualquer alteração no código, um engenheiro analisa o relatório e confirma o valor-alvo de cada DV. Trata-se de uma medida de segurança deliberada para casos ambíguos, tais como implementações parciais ou experiências abandonadas, em que a resposta correta requer o julgamento humano. Por exemplo, uma experiência pode ter sido implementada em 60 % e, posteriormente, abandonada. O controlo humano permite que o utilizador intervenha, decidindo, por exemplo, se deve concluir a implementação ou reverter a experiência antes de se prosseguir com a limpeza.

Fase 2: Limpeza paralela

Por cada DV confirmado, o orquestrador cria um agente de remoção dedicado — o Claude Opus —, sendo que cada um é executado na sua própria árvore de trabalho Git isolada. Podem ser executados até quatro agentes em simultâneo por repositório, sendo este número configurável. Cada um deles realiza uma limpeza completa e abre um pedido de integração atribuído ao proprietário do DV.

No interior de um agente de remoção

Cada agente de remoção executa um fluxo de trabalho iterativo da seguinte forma:

  1. Procure todas as referências ao DV, incluindo a constante, a função de envolventes e todos os locais de chamada.
  2. Defina uma estratégia de limpeza com base no tipo da variável dinâmica (booleana, inteira ou cadeia de caracteres) e na sua utilização.
  3. Aplique as alterações; substitua as chamadas ao wrapper pelo valor pretendido e simplifique as condições que agora são constantes.
  4. Remova a função de invólucro, a definição e a constante.
  5. Atualizar ou remover os testes afetados, como, por exemplo, eliminar ramos de teste «marcados como desativados».
  6. Confirme se a compilação foi bem-sucedida.
  7. Confirme se os testes foram bem-sucedidos.
  8. Verifique se a cobertura do patch é de, pelo menos, 95% nas linhas alteradas.
  9. Execute o linter Detekt.
  10. Se alguma validação falhar, identifique o problema, corrija-o e tente novamente.

A autonomia sobre um conjunto de código só funciona se as medidas de segurança forem efetivas. Existem quatro mecanismos que garantem a segurança dos agentes:

  • Isolamento da árvore de trabalho do Git: Cada agente trabalha numa árvore de trabalho dedicada, pelo que os agentes em execução simultânea nunca interferem uns com os outros, e uma limpeza mal sucedida é descartada simplesmente removendo a árvore de trabalho.
  • Um tempo limite fixo de uma hora por agente: isto garante que uma sessão bloqueada não se prolongue indefinidamente.
  • Validação da cobertura de patches do JaCoCo: isto garante uma cobertura de testes de, pelo menos, 95% nas linhas alteradas pelo agente.
  • Análise estática com o Detekt: Esta ferramenta garante o cumprimento das normas de estilo e deteta problemas comuns.

Também executamos o Gradle com a opção –no-daemon para eliminar a contaminação do estado entre árvores de trabalho durante as execuções paralelas. É fundamental referir que um agente não pode abrir um PR enquanto os testes e a análise estática não forem aprovados localmente.

Resultados

Criar um sistema é uma coisa, mas quando este funciona à escala da DoorDash, só há uma questão que importa: até que ponto funciona bem? Neste caso, funciona mesmo muito bem.  Das 50 sinalizações de código obsoleto mais recentes que o sistema processou, gerou um PR pronto para fusão em 45 delas, com um custo médio de 4,79 dólares e 13,8 minutos por limpeza, em vez das uma a duas horas que uma limpeza manual teria demorado. O sistema não introduziu quaisquer erros nem regressões ao longo do processo.

Como o avaliámos

Avaliámos o sistema com base nos 50 DVs obsoletos mais recentes que este processou em vários repositórios Kotlin. Classificámos cada um deles de acordo com a sua complexidade: 

  • Simples — Uma única verificação booleana num ou dois ficheiros; n=6
  • Médio — Várias utilizações em três a cinco ficheiros, com algumas alterações de teste; n=18
  • Complexo — condicionais aninhadas, dependências entre ficheiros, tipos não booleanos ou atualizações significativas nos testes; n=26 

Por tipo, 41 eram booleanos, seis eram cadeias de caracteres e três eram inteiros/longos. Cada PR foi avaliado com base em três critérios: aprovação no CI — incluindo o limite de 95 % de cobertura do patch —, correção do código e revisão por parte dos programadores. 

Resultados por nível de complexidade

Figura 2: Resultados da limpeza por nível de complexidade. As DVs simples são aceites a 100 % à primeira tentativa; as DVs de complexidade média atingem uma taxa de limpeza de 94 %; as DVs complexas apresentam mais revisões e falhas, mas ainda assim atingem 85 %.

Conforme ilustrado na Figura 2, 31 das 50 limpezas foram concluídas à primeira tentativa, 14 exigiram uma pequena revisão e cinco exigiram a intervenção de um engenheiro. A análise da complexidade reflete o limite de fiabilidade do sistema:

  • DVs simples: 100% limpos numa única passagem.
  • DVs médias: 94% (17/18) — 14 concluídas numa única passagem, três após uma revisão e uma que exigiu a intervenção de um engenheiro.
  • DVs complexas: 85% (22/26), mesmo com a remoção em cascata de parâmetros ao longo de mais de cinco camadas de métodos, condições aninhadas e uma refatoração profunda dos testes.

As 14 revisões foram menores e autocorretivas; seis apresentavam uma cobertura insuficiente do patch, exigindo mais uma iteração para adicionar testes, e oito consistiam numa remoção incompleta de código morto, com uma variável ou referência remanescente numa cadeia de chamadas profunda. No entanto, todas foram corrigidas numa única nova solicitação ao LLM.

A conclusão mais importante prendeu-se com o tipo de problema que surgiu. Em todas as 50 PRs geradas, não observámos quaisquer erros ou regressões introduzidas pelo código alterado. Cada um dos cinco casos de intervenção resumiu-se à profundidade da cadeia de chamadas com encadeamento de parâmetros entre interfaces; o agente removeu corretamente a maior parte do código morto, mas deixou escapar um ou dois elos numa longa cadeia que envolvia vários ficheiros. Por outras palavras, o limite de fiabilidade do sistema é a exaustividade, e não a correção, o que atenua as preocupações de que o sistema pudesse criar problemas maiores quando não fosse bem-sucedido.

Custo e tempo

Os custos e os prazos variam em função da complexidade, mas mantêm-se firmemente no âmbito da «execução em grande escala», tal como se pode ver na Tabela 1 e na Figura 3. 

A Tabela 1 apresenta uma análise detalhada das métricas médias e medianas de desempenho — nomeadamente, tempo e custo — para os 50 «feature flags» analisados, classificados de acordo com a sua complexidade. Estes dados revelam que, embora os «feature flags» mais complexos exijam, naturalmente, mais tempo e orçamento, o sistema continua a ser altamente eficiente, mesmo nas tarefas de limpeza mais difíceis.

ComplexidadenTempo médioTempo medianoCusto médioCusto mediano
Simples67,5 min8,2 min$2.69$2.50
Médio1810,4 min9,9 min$3.46$3.42
Complexo2617,7 min14,4 min$6.20$4.50

Tabela 1: Métricas de desempenho por nível de complexidade nas 50 «feature flags» avaliadas.

Figura 3: Custo da API em função do tempo real para cada DV, classificado por complexidade. Os DVs simples e médios agrupam-se na região de baixo custo e baixo tempo. Os DVs complexos estão mais dispersos, com alguns valores atípicos que se aproximam dos 40 minutos e dos 19 dólares. Os DVs com falha (×) tendem a apresentar tempos de execução mais longos.

Conforme ilustrado na Figura 3, os DVs simples e médios agrupam-se estreitamente no canto «barato e rápido». Os DVs complexos estão mais dispersos, com alguns valores atípicos a atingirem cerca de 40 minutos e cerca de 19 dólares. Mesmo no limite superior desse intervalo, o agente representa uma redução drástica no esforço de engenharia e nos custos, mesmo quando comparado com uma estimativa conservadora de uma hora do que seria necessário para remover manualmente um único DV.

O que aprendemos

  • A especialização dos modelos por função compensa. Utilizamos o Claude Sonnet para a orquestração, incluindo o contexto geral, a recolha de metadados simplificada e o planeamento, e o Claude Opus para os agentes de remoção, incluindo o raciocínio aprofundado sobre a semântica do código, as cadeias de chamadas e as dependências de teste. Reservar o modelo mais dispendioso para a fase de edição, que exige maior esforço cognitivo, reduziu o nosso custo global da API sem comprometer a qualidade da limpeza.
  • O isolamento da árvore de trabalho é imprescindível para os agentes paralelos. As nossas primeiras experiências sem isolamento da árvore de trabalho geraram condições de corrida, nas quais agentes simultâneos editavam os mesmos ficheiros e geravam comparações corrompidas. Uma árvore de trabalho dedicada por agente, juntamente com o Gradle na opção –no-daemon, tornou a limpeza paralela segura e permitiu que as falhas fossem descartadas.
  • A integração da experimentação em tempo real foi a decisão com maior impacto. Sem o estado atual de implementação, qualquer abordagem automatizada — baseada em regras ou em LLM — corre o risco de codificar de forma rígida o valor errado e de, inadvertidamente, perpetuar o comportamento incorreto. A leitura através do MCP acrescentou uma latência insignificante e eliminou toda uma classe de erros relacionados com valores-alvo.

Qualquer equipa que esteja a ponderar uma automatização semelhante deve ter em conta que os nossos resultados apontam para três pré-requisitos práticos: 

  • Uma fonte fiável do Estado de bandeira em que o navio se encontra
  • Isolamento da árvore de trabalho para edições em paralelo, e 
  • Os critérios de validação determinísticos são aplicados antes mesmo de se abrir um PR.

O que se segue

Estamos a expandir o sistema em duas vertentes:

  1. Um índice de confiança da fase 1 para aprovar automaticamente as correções de baixo risco. Por exemplo, uma variável de dependência (DV) booleana com uma taxa de implementação de 100%, cujo código por predefinição já corresponde ao da produção, o que reserva a verificação humana para casos verdadeiramente ambíguos.
  2. Uma verificação da qualidade do código após a limpeza. Isto permitiria detetar problemas semânticos, tais como nomes de variáveis que já não refletem a sua finalidade após a remoção de um sinalizador.

A limpeza de feature flags é o tipo de trabalho que, individualmente, é pequeno, mas, em conjunto, é enorme — especialmente à escala da DoorDash — e que é constantemente adiado. Ao combinar um orquestrador, agentes de remoção paralelos em árvores de trabalho isoladas, consultas à plataforma de experimentação em tempo real e portas de validação rigorosas, o nosso sistema de limpeza de feature flags transforma um atraso de milhares de horas num processo em segundo plano económico e fiável que produz PRs prontas para fusão para 90% das flags que trata, a um custo de cerca de 4,79 dólares e 14 minutos cada, sem regressões. O único obstáculo que ainda resta é a exaustividade nas cadeias de chamadas mais profundas, e é precisamente nisso que nos vamos concentrar a seguir.

Está interessado em desenvolver sistemas como este? A equipa de engenharia da DoorDash está a recrutar – venha ajudar-nos a desenvolver em grande escala.

O Flux é a plataforma de agentes baseada na nuvem da DoorDash destinada a engenheiros. Num único mês de 2026, utilizámos o Flux para automatizar 130 000 tarefas de engenharia. Em rápida expansão após a sua estreia no primeiro trimestre de 2026, o Flux já alimenta fluxos de trabalho em segundo plano de grande volume em toda a DoorDash, incluindo mais de 25 000 revisões de código automatizadas por semana, bem como mais de 300 guias de procedimentos únicos e mais de 10 000 chamadas utilizadas todas as semanas. Estes fluxos de trabalho podem ser executados de forma autónoma, em paralelo e 24 horas por dia.

Nesta publicação do blogue, vamos abordar as limitações que nos levaram a ir além das cargas de trabalho de agentes locais, executadas em computadores portáteis, as razões pelas quais optámos por desenvolver o Flux internamente, em vez de depender exclusivamente de agentes de codificação alojados, e os elementos básicos da plataforma, tais como as sandboxes de agentes, o gateway MCP, os playbooks e as superfícies de invocação, para tornar a delegação de agentes repetível e segura.

Casos de utilização do fluxo de trabalho de fundo do Flux

Tabela 1: Um resumo da utilização do Flux na DoorDash ao longo de um mês, abrangendo revisões automatizadas de código, execuções de playbooks e conclusões de tarefas em segundo plano

Onde começámos

Ao longo do último ano, os utilizadores que executam cargas de trabalho baseadas em agentes nos seus portáteis depararam-se rapidamente com algumas limitações:

  • Recursos e disponibilidade. Um portátil tem um número fixo de núcleos de CPU, memória limitada e uma bateria, todos partilhados com todas as aplicações instaladas. Os fluxos de trabalho do Agentic precisam frequentemente de executar tarefas que exigem grande capacidade de computação, tais como compilações, testes e pesquisas de grande dimensão em paralelo, o que faz com que os portáteis esgotem rapidamente a sua capacidade. Os fluxos de trabalho também dependem de o dispositivo estar ligado, conectado e disponível; o trabalho é interrompido quando um engenheiro fecha o portátil, perde a ligação ou se afasta.
  • Controlos de segurança. Os computadores portáteis têm, normalmente, um acesso alargado a credenciais e sistemas sensíveis, incluindo chaves SSH, sessões VPN e ferramentas autenticadas. Conceder a um agente autónomo esse mesmo nível de acesso cria um risco desnecessário e um raio de impacto potencialmente vasto. Os ambientes locais também tornam mais difícil definir com rigor o âmbito do acesso de um agente e a duração desse acesso.
  • Visibilidade e auditabilidade. Quando as cargas de trabalho são executadas em computadores portáteis individuais, a execução fica fragmentada e é difícil de monitorizar. Torna-se mais difícil perceber o que está a ser executado, onde está a ser executado, em nome de quem e quais os sistemas ou ficheiros que foram afetados.

A nossa abordagem para resolver estas questões é simples: 

Delegar tarefas a agentes de programação seguros e autónomos, para que os engenheiros possam dedicar mais energia à inovação, ao pensamento crítico e à resolução de problemas complexos.

Por que desenvolvemos o Flux internamente

Os agentes de programação alojados são úteis, mas impõem um compromisso difícil: ou enviar código sensível e o contexto de execução a um terceiro, ou abrir uma via de acesso desse terceiro de volta aos sistemas internos. Para a DoorDash, o problema mais complexo não era apenas conseguir que um agente escrevesse código; isso já está praticamente resolvido. Era proporcionar a esse agente o ambiente, as ferramentas, as permissões, as integrações e as restrições adequadas.

A nossa estratégia consiste em controlar os elementos básicos em torno do agente, incluindo orquestração, ambientes de teste, fluxos de trabalho, permissões, integrações e os agentes de contexto específicos da DoorDash necessários para um funcionamento eficaz. Também concebemos esses elementos básicos de forma modular, o que nos dá a flexibilidade de utilizar a melhor ferramenta de terceiros para cada tarefa ou de desenvolver soluções internamente quando é importante garantir um maior nível de segurança, integração, desempenho ou controlo sobre a experiência do utilizador. 

Estas primitivas democratizam a criação de fluxos de trabalho e tornam os sistemas mais adaptáveis a futuros casos de utilização. Como podem ser combinadas de diferentes formas, as equipas podem criar novos fluxos de trabalho para os agentes sem terem de reformular a infraestrutura subjacente nem impor a forma como cada engenheiro deve estruturar o seu fluxo de trabalho. Por exemplo, executamos as avaliações da nossa revisão de código na infraestrutura do Flux.

Elementos primitivos, não fluxos de trabalho

Conforme ilustrado na Figura 1, o Flux assenta em quatro elementos fundamentais da plataforma: sandboxes, o gateway do protocolo de contexto do modelo (MCP), playbooks e superfícies de invocação. Em conjunto, tornam a delegação de tarefas aos agentes repetível. Um playbook define o trabalho. Uma sandbox na nuvem proporciona ao agente um ambiente real para o realizar. Um gateway de agente controla os sistemas aos quais o agente pode aceder. E as superfícies de invocação permitem que os engenheiros iniciem e recebam trabalho a partir dos ambientes que já utilizam.

Figura 1: As quatro primitivas da plataforma que compõem o Flux — sandboxes, o gateway MCP, os playbooks e as superfícies de invocação — e a forma como se interligam para transformar uma tarefa num trabalho que um agente possa executar com segurança

As sandboxes proporcionam o ambiente de execução

Os agentes locais funcionam bem para o desenvolvimento interativo, mas não são adequados para fluxos de trabalho não supervisionados. Dependem dos portáteis de cada engenheiro, competem pelos recursos locais, são difíceis de auditar e não se adaptam de forma eficiente a tarefas paralelas.

O Flux transfere a execução para ambientes isolados na nuvem, suportados por microvirtuais (microVMs) Firecracker, que garantem o isolamento ao nível do hardware. Cada ambiente é provisionado com os repositórios, as ferramentas de desenvolvimento, os segredos e as dependências de tempo de execução necessárias para a tarefa, proporcionando aos agentes um espaço de trabalho de engenharia completo e, ao mesmo tempo, oferecendo à DoorDash um modelo consistente de execução, segurança e observabilidade.

O controlo desta camada permite-nos dar suporte a fluxos de trabalho de engenharia reais, incluindo alterações em vários repositórios e vários pedidos de integração a partir de uma única sessão. O Flux tem um objetivo de nível de serviço no 95.º percentil inferior a cinco segundos para toda a configuração de ponta a ponta — desde o arranque da microVM até à clonagem dos repositórios necessários, passando pela instalação das ferramentas de compilação e pela configuração dos conjuntos de testes do agente de codificação suportados.

O gateway MCP proporciona acesso controlado

Os agentes precisam de ter acesso aos sistemas que os engenheiros utilizam diariamente, incluindo integração contínua (CI), plataformas de observabilidade, sistemas de acompanhamento de problemas, ferramentas de implementação, pesquisa de código, documentação e metadados de serviços. No entanto, conceder acesso amplo e sem restrições não deve ser a opção predefinida.

O Flux liga os agentes aos sistemas internos através de um gateway MCP interno denominado «Agent Gateway». Cada playbook declara as ferramentas de que necessita, e o Flux concede apenas as permissões específicas necessárias para essa tarefa. Todas as ações são registadas, criando um registo de auditoria claro.

Esta arquitetura de gateway proporciona-nos um ponto de controlo centralizado para a autenticação, autorização, observabilidade, acompanhamento da utilização e aplicação de políticas, o que torna o acesso dos agentes mais seguro e mais fácil de gerir em grande escala.

Os manuais definem o trabalho

Um «playbook» é uma unidade reutilizável de trabalho autônomo — o equivalente a um contentor Docker para competências e tarefas executadas por agentes na plataforma Flux. Definido num único ficheiro YAML de marcação, agrupa a tarefa, os dados de entrada, o contexto, as competências, as ferramentas, as permissões, a validação, os resultados esperados e os limites de segurança necessários para executar o trabalho de forma consistente.

Os playbooks podem combinar etapas orientadas por agentes, que proporcionam flexibilidade e capacidade de decisão, com etapas determinísticas, que oferecem previsibilidade, custos mais baixos e uma validação mais fácil. Isto permite que as equipas transfiram a lógica entre a execução orientada por agentes e o código convencional à medida que os requisitos evoluem, sem necessidade de redesenhar o fluxo de trabalho.

As interfaces de invocação vão ao encontro dos programadores onde quer que estes se encontrem

O mesmo manual de procedimentos pode ser acionado a partir do Slack, do GitHub, do cron, da CLI ou de uma funcionalidade conversacional. Isso significa que as equipas podem definir um fluxo de trabalho uma única vez e invocá-lo a partir da plataforma que melhor se adequar ao momento:

  • O Slack para a delegação colaborativa
  • GitHub para a automatização de PR e CI
  • Cron para manutenção recorrente
  • CLI para controlo direto por parte do programador ou invocada através de uma skill

É isso que torna o Flux fácil de adotar.

Lições aprendidas

A criação do Flux ensinou-nos tanto sobre a adoção de produtos como sobre infraestruturas, incluindo:

  • Comece por um âmbito restrito para ganhar confiança. Começámos pela revisão automatizada de código, em vez de tentarmos automatizar todo o ciclo de vida do desenvolvimento de software. A revisão de código era frequente, mensurável e fácil de avaliar pelos engenheiros. Isso proporcionou-nos um fluxo de trabalho de produção em que pudemos ajustar a qualidade, a latência, o custo e o comportamento antes de expandirmos para a triagem de CI, tarefas de plantão, manuais de manutenção e desenvolvimento orientado por tickets.
  • Tornar o trabalho visível. A nossa primeira integração com o Slack criou canais privados para cada execução de um agente. Isso tornou o Flux útil para os indivíduos, mas não criou hábitos de equipa. Transferir o trabalho para tópicos públicos alterou o padrão de adoção. Os engenheiros podiam ver o que os outros delegavam, acompanhar o progresso no Flux, analisar os resultados e construir confiança em conjunto.
  • Os manuais operacionais precisam de apoio. Os fluxos de trabalho reutilizáveis não surgem simplesmente porque a plataforma existe. Os workshops e os hackathons ajudaram as equipas a transformar tarefas operacionais repetitivas em manuais operacionais. Os elementos básicos tornaram a automatização possível; o apoio ajudou as equipas a reconhecer quais os fluxos de trabalho que valia a pena codificar.

O que se segue

Em publicações futuras, iremos aprofundar os conceitos básicos da plataforma que permitem o funcionamento do Flux, bem como a experiência do programador na criação de novos fluxos de trabalho. Iremos também abordar as aplicações que desenvolvemos com base nela, incluindo o Flux Responder, o nosso agente interno do Slack.

Agradecimentos

Agradecemos a Adam Rogal, Adam Yarger, Andy Fang, Ashwin Kachhara, Fan Xia, Ivan Rudovol, Jason Prasad, Jialu Deng, Justin Block, Justin Deocampo, Justin Fan, Keith Lyall, Praneet Singh, Sean Chen, Tyler Berrett e Volanda Zhu pelas suas contribuições para a plataforma e para este artigo.

Mantenha-se informado com as actualizações semanais

Subscreva o nosso blogue de Engenharia para receber actualizações regulares sobre todos os projectos mais interessantes em que a nossa equipa está a trabalhar

Os agentes de IA tornam-se úteis quando conseguem agir em sistemas reais. Na DoorDash, isso significa aceder a APIs internas, sistemas de engenharia, plataformas de observabilidade, sistemas de gestão de tickets, bases de conhecimento e produtos SaaS de terceiros. O Protocolo de Contexto do Modelo (MCP) facilitou a exposição dessas ferramentas, proporcionando aos agentes e servidores uma forma comum de descrever, descobrir e invocar funcionalidades.

À medida que os agentes passaram das experiências para os fluxos de trabalho reais, deparámo-nos com um problema diferente. Embora o MCP tivesse padronizado o formato de uma chamada de ferramenta, não respondia a questões-chave relacionadas com a produção em torno dessa chamada:

  • Que agente está autorizado a utilizar esta ferramenta?
  • Em nome de que utilizador, equipa ou serviço está a agir?
  • Que credencial deve ser utilizada?
  • Que subconjunto de ferramentas é que o agente deveria sequer ver?
  • Como é que revogamos o acesso?
  • Como é que sabemos o que aconteceu depois da chamada?

Estas necessidades aumentam rapidamente. Um agente de programação pode precisar do GitHub, do Jira, da pesquisa de código, da integração contínua (CI), da observabilidade e da documentação. Um servidor MCP de terceiros pode disponibilizar centenas de ferramentas, quando um fluxo de trabalho precisa apenas de cinco. Um agente que lida diretamente com os utilizadores pode precisar da autorização OAuth de um utilizador, enquanto a automação da equipa deve utilizar uma entidade de serviço não pessoal. Se cada equipa resolver estas questões de forma independente, cada par agente-ferramenta acaba por ter o seu próprio código de autenticação, fluxo OAuth, gestão de segredos, catálogo de ferramentas, limites de taxa e registos.

Foi por isso que criámos um Agent Gateway, para tornar o acesso às ferramentas dos agentes uma funcionalidade da plataforma. Trata-se de um único ponto de entrada regulamentado, através do qual os agentes descobrem e acedem às ferramentas. O gateway autentica o utilizador, verifica a autorização, expõe apenas as ferramentas aprovadas, introduz as credenciais corretas, encaminha para o servidor MCP a jusante e regista um evento de utilização estruturado para cada chamada.

O gateway não é apenas um proxy. É o plano de controlo do ecossistema de agentes. Determina quem pode aceder às ferramentas, seleciona as ferramentas que os agentes podem ver, agrupa as ferramentas em interfaces orientadas para tarefas e proporciona à DoorDash um único local para monitorizar, limitar o número de acessos, revogar e melhorar o acesso dos agentes às ferramentas.

Problema: O acesso às ferramentas tem três partes

Embora o MCP ajude os agentes a aceder às ferramentas, o problema mais complexo em ambiente de produção tem três dimensões:

  1. O primeiro é o acesso. A plataforma precisa de saber quem está a ligar, se essa pessoa está autorizada e qual o modelo de credenciais aplicável: identidade interna, token mantido pelo gateway, OAuth por utilizador ou acesso por entidade de serviço.
  2. O segundo aspeto é a seleção das superfícies das ferramentas. Os agentes não devem receber um conjunto bruto de todas as ferramentas expostas pelos servidores MCP a jusante. Catálogos mais pequenos e relevantes para a tarefa melhoram a segurança, reduzem a confusão dos modelos e facilitam a operação dos agentes.
  3. O terceiro aspeto é o das operações. As chamadas às ferramentas requerem limites de taxa, rastreios, métricas, eventos de utilização, atribuição de custos, metadados de propriedade e visibilidade da produção.

O nosso Agent Gateway existe porque o acesso, a curadoria e as operações devem estar reunidos numa única plataforma partilhada, e não duplicados em cada agente e em cada servidor MCP.

Arquitetura de gateway

O gateway tem duas componentes principais: um proxy e um registo, conforme ilustrado na Figura 1 abaixo. O proxy constitui o plano de dados; recebe cada pedido MCP, autentica o solicitante, autoriza a ação, aplica limites de taxa, insere credenciais, reencaminha o pedido e emite dados de observabilidade. O registo é a fonte de verdade do plano de controlo; armazena agentes, servidores MCP, proprietários, configurações de transporte, modos de autenticação, políticas, catálogos de ferramentas detetadas e configurações da superfície das ferramentas.

Figura 1: Arquitetura de alto nível do Agent Gateway

Esta divisão confere ao gateway algumas características que são difíceis de obter em integrações ponto a ponto:

  • Os agentes utilizam um padrão único e consistente de pontos finais MCP, em vez de terem de aprender o modelo de autenticação e encaminhamento de cada servidor a jusante.
  • Os proprietários das ferramentas registam as capacidades uma única vez, associam a propriedade e a política e visualizam a utilização em produção.
  • As equipas de segurança dispõem de um único local para controlar o acesso, revogar autorizações e auditar chamadas.
  • As equipas da plataforma podem melhorar o percurso partilhado uma única vez e fazer com que todos os agentes beneficiem dessa melhoria.

Também mantemos separados os limites de confiança internos e externos. Os fluxos de trabalho internos da DoorDash e os casos de utilização dos agentes voltados para o exterior são executados através de planos de proxy distintos, com bibliotecas partilhadas e conceitos de registo. Isso limita o alcance de um eventual incidente voltado para a Internet e permite que cada modelo de autenticação evolua de forma independente.

Identidade, autorização e segredos centralizados

Cada pedido de gateway está associado a um remetente: um utilizador, um serviço ou um agente que atua com um contexto de utilizador delegado. Essa identidade é mantida ao longo do processo de autorização, injeção de credenciais, encaminhamento e observabilidade, para que cada ação a jusante possa ser atribuída.

O gateway verifica a autorização de forma centralizada. As questões relacionadas com a política não se limitam a determinar se este utilizador pode aceder a este servidor. Podem também ser:

  • Este agente pode aceder a este servidor?
  • Este utilizador pode aceder a esta ferramenta através deste agente?
  • Esta ferramenta pode ser exposta neste ambiente?
  • Este utilizador pode utilizar uma variante com capacidade de gravação ou apenas uma de leitura?
  • Este fluxo de trabalho pode utilizar uma entidade de serviço da equipa ou requer OAuth por utilizador?

Como a política está definida no gateway, a DoorDash dispõe de um único local para alterar e revogar o acesso. Os responsáveis pelas ferramentas evitam ter de reconfigurar a autorização para cada servidor, e os criadores de agentes evitam incorporar decisões de segurança nos prompts ou no código da aplicação.

O tratamento das credenciais segue o mesmo padrão, conforme ilustrado na Tabela 1:

Modo de autenticaçãoQuem é o titular da credencialO que o gateway faz
Identidade de serviço internaInfraestrutura da DoorDashEncaminha o contexto do chamador verificado para os serviços internos
Token detido pela GatewayArmazenamento seguro de chaves de gatewayInjeta um token de fornecedor ou de serviço sem o expor ao agente
OAuth por utilizadorArmazenamento de autorizações encriptado por utilizadorInserir e atualizar o token do utilizador para ações no âmbito do utilizador
Serviço principalDiretor da equipa gerida pela GatewayAs entidades emissoras ou os intermediários emitem credenciais não pessoais de curta duração

Os agentes não devem deter credenciais em bruto, tais como chaves de API de fornecedores, tokens de atualização OAuth ou autorizações humanas emprestadas para automatizações da equipa. O gateway mantém esses limites explícitos e auditáveis.

OAuth por utilizador sem interromper a vez do agente

As ferramentas de terceiros com âmbito de utilizador requerem a autorização do utilizador. Ler os documentos de um utilizador, criar um ticket em nome desse utilizador ou atualizar dados de SaaS em seu nome não pode ser feito de forma segura utilizando uma chave partilhada. Antes de implementarmos o gateway, cada equipa tendia a criar o seu próprio fluxo OAuth, armazenamento de tokens e experiência do tipo «ligar-se primeiro».

O gateway centraliza a concessão de autorização OAuth. A unidade de acesso está limitada ao agente, ao utilizador e ao servidor. Na primeira chamada que exija autorização, o gateway inicia o fluxo OAuth do fornecedor, armazena os tokens de acesso e de atualização resultantes, encriptados, e insere o token do utilizador em chamadas futuras. Os agentes nunca têm acesso ao token em bruto.

Para clientes que suportam a autenticação MCP, o gateway pode suspender a chamada da ferramenta, solicitar ao cliente que apresente um aviso de ligação e retomar a chamada original após a autorização do utilizador. Para clientes sem suporte à autenticação, o gateway devolve uma resposta estruturada indicando que é necessária autorização, acompanhada de um URL de ligação, conforme ilustrado na Figura 2. Isto transforma a ligação, que seria uma etapa falhada, numa parte recuperável da chamada da ferramenta.

Figura 2: Processo de estabelecimento de contacto para a recolha de informações

Superfícies de ferramentas selecionadas: Pacotes e filtragem

Os agentes não pensam naturalmente em servidores MCP. Pensam em tarefas. Um agente de programação não quer o GitHub, o Jira, a observabilidade e a documentação como etapas de configuração separadas; quer o conjunto de ferramentas necessário para investigar um problema, modificar código, abrir um pedido de integração, inspecionar a integração contínua e compreender o comportamento em produção.

Ao mesmo tempo, a maioria dos servidores MCP a jusante disponibiliza muito mais ferramentas do que qualquer agente deveria utilizar. Os servidores de terceiros podem publicar centenas de operações, incluindo ações administrativas, ações destrutivas, APIs de faturação e funcionalidades específicas de fornecedores de nicho. A maioria dos fluxos de trabalho da DoorDash necessita apenas de um pequeno subconjunto aprovado dessas funcionalidades.

O gateway resolve ambos os problemas através de interfaces de ferramentas selecionadas, conforme ilustrado na Figura 3. Os pacotes combinam ferramentas de vários servidores MCP num único ponto de acesso lógico do MCP. Os filtros determinam quais as ferramentas de cada servidor que são disponibilizadas para um determinado pacote, agente, grupo de utilizadores, ambiente ou público-alvo.

Por exemplo, um pacote de ferramentas de desenvolvimento pode incluir:

  • ferramentas selecionadas do GitHub para fluxos de trabalho de repositórios e pedidos de integração;
  • ferramentas selecionadas do Jira para pesquisa e atualização de problemas;
  • ferramentas de observabilidade selecionadas para registos, métricas e rastreios;
  • ferramentas selecionadas de pesquisa de código e de documentação; e
  • ferramentas selecionadas de implementação ou de sinalizadores de funcionalidades.

Figura 3: Experiência do utilizador com o pacote MCP incluído

O agente liga-se a um URL do gateway, como, por exemplo, um ponto final das ferramentas de desenvolvimento. Por trás desse URL, o gateway distribui o conteúdo de `tools/list` pelos servidores do conjunto, aplica filtros de autorização e de ferramentas, atribui namespaces ou aliases aos nomes das ferramentas quando necessário e devolve um catálogo coerente. Quando o agente invoca `tools/call`, o gateway volta a aplicar a política, encaminha a chamada para o servidor a jusante correto e aplica o modelo de credenciais desse servidor.

Isto proporciona aos agentes uma interface de qualidade, em vez de uma lista bruta de funcionalidades a jusante, e inclui elementos como ferramentas aprovadas, nomes estáveis, descrições mais claras, metadados de propriedade e pacotes específicos para cada público-alvo. As equipas de engenharia, análise de dados, operações de suporte e pacotes externos podem todas utilizar as mesmas primitivas do gateway, ao mesmo tempo que expõem interfaces de ferramentas diferentes.

As vantagens são práticas:

  • A configuração do agente é mais simples: um único ponto de acesso, em vez de vários pontos de acesso de servidor.
  • A deteção de ferramentas é mais segura: os agentes só visualizam as ferramentas que se encontram dentro do limite aprovado.
  • O comportamento dos modelos melhora: catálogos mais reduzidos diminuem as opções irrelevantes e a confusão em relação às ferramentas.
  • A autorização continua a ser centralizada: a descoberta e a invocação aplicam a mesma política.

É aqui que o gateway se torna mais do que um proxy de acesso. Ele determina o que os agentes podem descobrir, o que podem chamar e a quantidade de contexto irrelevante que transportam. Uma seleção cuidada de ferramentas pode fazer a diferença entre um agente que escolhe a ferramenta certa e outro que vagueia por um catálogo de APIs excessivamente extenso.

Observabilidade, atribuição de custos e proteção a jusante

Como todas as chamadas passam pelo gateway, cada pedido pode emitir um evento estruturado com:

  • o servidor, a ferramenta, o pacote e a equipa responsável;
  • o utilizador, o agente, o serviço e a plataforma envolvidos na chamada;
  • resultado da autorização, código de estado, origem do erro e análise da latência;
  • tamanho do pedido e da resposta; e
  • metadados de custos comunicados a jusante, quando disponíveis.

O gateway também fornece métricas relativas ao volume de pedidos, à latência por ferramenta, às decisões de autorização, aos resultados da atualização do OAuth, às decisões de limitação de taxa, às ligações de streaming e às falhas a montante. A propagação de rastreios permite que as equipas acompanhem uma chamada desde o agente, passando pelo gateway, até ao servidor a jusante.

Isto tem um valor operacional direto; a equipa de segurança pode auditar os acessos, as equipas da plataforma podem identificar agentes problemáticos, os responsáveis pelas ferramentas podem analisar a adoção e os erros, e as equipas de infraestrutura podem atribuir os custos.

Este mesmo mecanismo protege os sistemas a jusante. Os limites de taxa podem ser definidos por servidor, ferramenta, entidade de origem, utilizador, pacote ou tipo de entidade de origem. Os novos limites podem ser testados em modo de simulação antes de serem aplicados, mostrando o que teria sido rejeitado sem afetar o tráfego de produção.

O gateway transforma a governação em dados, em vez de depender de cada equipa para registar os campos corretos e aplicar os limites adequados.

Integração em regime de autoatendimento

Um gateway só funciona se as equipas preferirem utilizá-lo em vez de o contornarem. O registo, a deteção, a filtragem e a gestão de pacotes têm de ser todos em regime de autoatendimento.

Através da interface de utilizador e da API do plano de controlo, as equipas podem registar servidores e agentes MCP, configurar a autenticação, detetar ferramentas, atribuir propriedade, definir filtros, adicionar ferramentas a pacotes e analisar a utilização em produção.

O ciclo de integração do gateway segue os seguintes passos:

  1. Registe o servidor MCP.
  2. Descubra o catálogo de ferramentas do Raw através de tools/list.
  3. Selecione e aprove as ferramentas que a DoorDash pretende disponibilizar.
  4. Definir o modo de autenticação, a titularidade e a política.
  5. Adicione as ferramentas aprovadas a um ou mais pacotes.
  6. Acompanhe o tráfego, a latência, os erros, as decisões de autorização e os custos.

Uma governança que exija a utilização de tickets não é escalável. O caminho mais fácil tem de ser mais simples do que copiar um segredo para um agente e estabelecer ligação diretamente a um servidor.

O que mudou

O portal oferece a cada grupo um benefício diferente:

  • Os criadores de agentes têm à disposição uma integração, um catálogo selecionado e não precisam de se preocupar com a autenticação a jusante, OAuth, gestão de segredos ou encaminhamento por ferramenta.
  • Os proprietários das ferramentas dispõem de um canal de distribuição gerido, com controlo de acesso, exposição aprovada das ferramentas e dados sobre a utilização em produção.
  • As equipas de segurança dispõem de políticas centralizadas, informações confidenciais, autorizações OAuth, revogação e registos de auditoria.
  • As equipas da plataforma ganham vantagem; a identidade, a limitação de taxas, a observabilidade, a qualidade das ferramentas, a atribuição de custos e a experiência do criador melhoram, tudo num único local.
  • Os agentes têm acesso a catálogos mais reduzidos, nomes de ferramentas mais claros, pacotes orientados para tarefas, menos opções irrelevantes e fluxos de ligação recuperáveis.

Adoção

A adoção é o verdadeiro teste de uma plataforma: as equipas só a utilizam se for mais fácil do que o acesso direto. Nesse sentido, o Agent Gateway tornou-se o caminho predefinido para o acesso dos agentes às ferramentas em toda a equipa de engenharia da DoorDash:

  • Estão registados mais de 200 servidores MCP atrás do gateway, expondo, no seu conjunto, milhares de ferramentas organizadas em subconjuntos aprovados e orientados para tarefas específicas, em vez de catálogos em bruto.
  • Mais de 30 agentes e serviços, utilizados por milhares de colaboradores, acedem a essas ferramentas através do gateway e nenhum deles lida com credenciais em bruto. 
  • Todas as semanas, milhões de chamadas de ferramentas são encaminhadas através do Agent Gateway, sendo cada uma delas autenticada, autorizada e registada como um evento de utilização estruturado.
  • A integração é autónoma e rápida. O registo de um novo servidor MCP e das suas ferramentas demora apenas alguns minutos, e associar um agente a uma ferramenta já registada é ainda mais rápido.

Lições aprendidas

A criação e a expansão do gateway redefiniram a forma como encaramos o acesso dos agentes às ferramentas. Há algumas lições que se destacam, não apenas na DoorDash:

  • O MCP resolve a questão da invocação, mas não a da governação. A identidade, as políticas, os segredos, a curadoria, a observabilidade e a revogação tornam-se os aspetos mais complexos.
  • O catálogo de ferramentas descoberto é uma interface, o que significa que os nomes, as descrições, a filtragem, o agrupamento e o público-alvo são importantes.
  • Os pacotes são a unidade ideal para os fluxos de trabalho. Os agentes precisam de conjuntos de ferramentas orientados para tarefas, e não de listas de servidores.
  • A filtragem de ferramentas melhora tanto a segurança como a qualidade dos agentes, ao apresentar um conjunto mais reduzido e claro de ações.
  • As credenciais devem ser armazenadas no gateway, onde podem ser atualizadas, auditadas e revogadas de forma centralizada.
  • A ausência de autorizações OAuth constitui um estado normal, e não uma exceção, no âmbito do protocolo.
  • O canal regulamentado tem de ser de autoatendimento; caso contrário, as equipas recorrerão diretamente a outras vias.

O que se segue

O próximo grande investimento incidirá no reforço da identidade dos agentes e na delegação de poderes aos utilizadores. O modelo-alvo atribui a cada agente uma identidade criptográfica e permite que o gateway emita credenciais delegadas de curta duração, limitadas ao utilizador, ao agente, à tarefa e à ferramenta de destino. Isto confere à pista de auditoria dois titulares reais: o utilizador e o agente.

Estamos também a investir em ferramentas de desenvolvimento, incluindo relatórios de qualidade e segurança, verificações de descrições de ferramentas de risco, deteção de segredos ou informações de identificação pessoal em erros, criação escalonada de servidores, registo automático, deteção dinâmica de ferramentas e fluxos de eventos de chamadas de ferramentas censurados para fins de análise e conformidade.

A descoberta dinâmica dá continuidade ao mesmo tema. Em vez de fornecer a um agente todas as ferramentas num pacote, o gateway pode utilizar o contexto da tarefa, a política e os sinais de utilização para apresentar apenas as ferramentas que provavelmente serão úteis para a tarefa em curso.

Conclusão

O Agent Gateway transforma o acesso às ferramentas dos agentes, que antes exigia um trabalho repetitivo de integração, numa funcionalidade da plataforma partilhada. O MCP facilitou a descrição e a invocação de ferramentas; o gateway torna esse acesso regulamentado, supervisionado, monitorizável e escalável.

Agradecimentos

Introdução

Criámos o Ask DoorDash numa plataforma comum que permite às equipas de cada domínio desenvolver e aperfeiçoar os seus agentes sem terem de reconstruir os sistemas subjacentes. Avaliámos a plataforma com base em dois resultados práticos: a rapidez com que as equipas conseguiam adicionar funcionalidades e domínios e a rapidez com que conseguiam avaliar e implementar melhorias em termos de custos, qualidade e latência.

Lançámos o Ask DoorDash com suporte para restaurantes e mercearias em cerca de dois meses e, desde então, já processou mais de dois milhões de conversas. A adição do Reservations, o nosso terceiro agente de domínio, demorou uma semana, o que é cerca de 10 vezes mais rápido do que a criação dos agentes de domínio iniciais.

O nosso conjunto de testes de avaliação partilhado e os nossos controlos de implementação fornecem às equipas indicadores claros de qualidade à medida que avançam com as alterações para produção. No prazo de uma semana após o lançamento de um novo LLM, avaliámo-lo e implementámo-lo, reduzindo a latência p50 em 35 % sem qualquer diminuição nos índices de qualidade. Uma atualização subsequente do modelo reduziu a latência p50 em mais 40 %.

Este artigo explica como escolhemos o que deveria fazer parte da plataforma, como os seus componentes se articulam entre si e as escolhas que estiveram na base dessas decisões. Segue-se à nossa visão geral de engenharia, à nossa análise aprofundada sobre inteligência e à nossa análise aprofundada sobre avaliação. Seguir-se-á uma análise aprofundada sobre a experiência do utilizador.

Como escolhemos o que padronizar

Só centralizávamos uma funcionalidade quando vários domínios precisavam dela e se implementações separadas pudessem causar problemas de fiabilidade ou operacionais. A orquestração, a memória, o acesso aos modelos, o rastreio, a infraestrutura de avaliação e os controlos de implementação cumpriam esse critério. As equipas de domínio mantiveram a responsabilidade pelas instruções, competências, ferramentas, critérios de avaliação e escolhas de modelos que definem os seus agentes.

Sempre que existia uma norma da indústria adequada, adotámo-la. O Agent2Agent (A2A) definiu a forma como os agentes comunicam, enquanto o Google Agent Development Kit (ADK) forneceu a estrutura para os criar e executar. Estas normas limitaram, por vezes, as nossas opções de conceção, mas proporcionaram às equipas contratos comuns e pouparam-nos de ter de desenvolver equivalentes proprietários.

No caso de problemas recorrentes para os quais não existia uma norma adequada, criámos nós próprios a capacidade necessária. Uma regressão no código partilhado poderia afetar vários agentes, pelo que os controlos de avaliação e implementação eram obrigatórios. Todas as alterações partilhadas tinham de passar por verificações de qualidade comuns e por medidas de segurança de lançamento.

Consultar a arquitetura da DoorDash

A decisão arquitetónica central consistiu em determinar o que cada agente deveria partilhar e o que cada domínio deveria controlar. Criámos um percurso de execução partilhado em torno de agentes especializados, controlados por cada domínio. Um único agente de uso geral tornar-se-ia cada vez mais difícil de compreender à medida que assumisse mais responsabilidades, enquanto pilhas de agentes totalmente independentes duplicariam a infraestrutura e gerariam experiências inconsistentes.

Como mostra a Figura 1, cada pedido entra através do Gateway. O Gateway autentica o utilizador, reúne o contexto do ponto de entrada e faz a tradução entre as interfaces HTTP e de streaming do cliente e o protocolo A2A da plataforma. Em seguida, encaminha o pedido para o Orchestrator, que seleciona o agente de domínio para cada turno, preservando a continuidade à medida que a conversa transita entre domínios. O agente selecionado carrega as competências adequadas e utiliza ferramentas do Model Context Protocol (MCP) para interagir com os serviços existentes da DoorDash.

Para além desse caminho de solicitação, a plataforma disponibiliza o estado da sessão, memória, artefactos, acesso a modelos, rastreio, infraestrutura de avaliação e controlos de implementação. Os agentes de domínio utilizam estes serviços comuns, mantendo ao mesmo tempo o controlo sobre as suas instruções, competências, ferramentas e critérios de avaliação.

Figura 1: O Ask DoorDash separa o comportamento dos agentes específico de cada domínio das capacidades partilhadas de execução e produção utilizadas em todo o sistema.

Uma porta de entrada para experiências responsivas e multimodais

Os gateways dos clientes da DoorDash foram concebidos para APIs que devolvem uma resposta completa rapidamente. Os agentes funcionam de forma diferente. O primeiro texto pode estar pronto enquanto o agente ainda está a aceder a ferramentas, e a resposta final pode combinar texto descritivo com cartões interativos de lojas ou artigos. Precisávamos de processar esse trabalho em tempo real sem ter de explicar a cada cliente como os agentes comunicam.

O Gateway encarrega-se desta conversão. Autentica o utilizador, acrescenta contexto sobre onde a conversa teve início e converte os pedidos HTTP dos clientes em pedidos A2A. No caminho de retorno, utiliza o SDK de IA da Vercel para enviar atualizações de texto e conteúdos dos widgets aos clientes através de Server-Sent Events (SSE). Os clientes apresentam as atualizações à medida que estas chegam, para que os utilizadores possam acompanhar o progresso enquanto o agente conclui a sua vez.

A criação e a operação do Gateway exigiram algum trabalho, mas permitiram concentrar tarefas que, de outra forma, seriam duplicadas em todos os clientes da DoorDash. Os agentes podem ser alterados no âmbito de um único contrato de cliente estável, sem necessidade de lançamentos coordenados por parte dos clientes. Um novo agente de domínio reutiliza o percurso de autenticação e transmissão já existente, em vez de se integrar separadamente com cada aplicação.

Um assistente, agentes especializados na área

Uma conversa pode passar de um domínio para outro sem aviso prévio. Um utilizador pode começar por dizer «Quero que me entreguem uma tarte de frango em menos de 30 minutos» e, em seguida, decidir: «Na verdade, quero fazê-la em casa». O primeiro pedido pertence ao domínio «Restaurantes» e o segundo ao domínio «Mercearia», mas a experiência tem de parecer uma única conversa.

Experimentámos uma arquitetura com um único agente, mas os domínios «Restaurante», «Mercearia» e «Reservas» dependem de ferramentas, políticas e critérios de avaliação diferentes. A sua combinação tornaria o agente mais difícil de testar e obrigaria todos os domínios a seguirem o mesmo ciclo de lançamento. Por isso, cada domínio dispõe de um agente especializado. O Orchestrator analisa a conversa e encaminha cada resposta para o agente adequado através do A2A.

Esse encaminhamento aumenta a latência e os tokens de entrada, mesmo quando a conversa permanece no mesmo domínio. Para reduzir este custo, o Orchestrator atribui as respostas subsequentes ao agente do domínio selecionado. Esta atribuição permite que esse agente trate diretamente os pedidos subsequentes, sem necessidade de mais uma etapa de encaminhamento. Se a conversa mudar de direção, o agente do domínio reconhece a mensagem fora do âmbito e devolve o controlo ao Orchestrator. O Orchestrator reencaminha a solicitação dentro da mesma vez, de forma invisível para o utilizador.

Figura 2: O «pinning» evita uma chamada ao Orchestrator nos turnos seguintes, permitindo, ainda assim, que a conversa transite entre domínios.

As competências mantêm o contexto e os custos dentro dos limites

Os agentes especializados impediram que os domínios «Restaurante», «Mercearia» e «Reservas» partilhassem uma única janela de contexto enorme. No entanto, à medida que cada agente ganhava novas capacidades, o mesmo problema de contexto surgiu dentro de cada domínio. As novas funcionalidades adicionavam instruções e ferramentas a cada turno, mesmo quando esse turno nunca as utilizava. Isso aumentou o custo dos tokens de entrada, e as instruções sobrepostas ou contraditórias confundiam o agente.

Adicionámos suporte a competências ao nosso sistema para modularizar instruções e ferramentas. Em cada turno, o agente carrega apenas o que é relevante. Um utilizador pode começar por dizer: «Encontra-me um restaurante tailandês com boas avaliações que faça entregas em menos de 30 minutos», e depois dizer: «Mostra-me o menu do primeiro e adiciona pad see ew ao meu carrinho.» Ambos os pedidos ficam a cargo do agente «Restaurante», mas o primeiro carrega a competência de pesquisa e descoberta. O segundo carrega a competência de encomenda pelo carrinho, que trata da navegação pelo menu e das ações no carrinho.

O carregamento dinâmico acrescenta um problema de seleção. O seletor tem de reconhecer quando uma capacidade é necessária sem carregar contexto não relevante com demasiada frequência; por isso, avaliamos a seleção de competências como parte do comportamento do agente. A complexidade adicional valeu a pena, pois as equipas puderam adicionar capacidades sem ter de expandir o contexto de cada turno.

Para avaliar o efeito, contámos os tokens presentes nas instruções básicas e nas instruções carregadas da competência do agente. Excluímos o histórico de conversas, as mensagens dos consumidores, os esquemas das ferramentas e os resultados das ferramentas. A mediana das respostas no âmbito da competência utilizou menos de metade dos tokens de instrução em comparação com o prompt do sistema monolítico.

AgenteInstruções básicas e sobre as habilidades, com todas as habilidades carregadasMédia de instruções com âmbito de competências por turnoRedução
Restaurante~42 000 fichas

~20 000 fichas50%+
Mercearia~25 000 fichas~10 000 fichas60%

Acesso seguro e reutilizável aos serviços através do MCP

O Ask DoorDash necessita de acesso aos serviços do DoorDash para pesquisar lojas e artigos, consultar ementas, gerir carrinhos de compras e agir em nome do utilizador. As APIs subjacentes a estas operações pressupõem código de aplicação determinístico. Um LLM que seleciona operações em tempo de execução necessita de uma interface mais restrita. Expor as APIs diretamente obrigaria o modelo a interpretar interfaces de baixo nível. Codificar permissões e regras de negócio apenas no prompt acrescentaria contexto sem garantir a sua aplicação.

Criámos uma camada partilhada do Protocolo de Contexto do Modelo (MCP) entre os agentes e as APIs da DoorDash. Cada ferramenta MCP expõe uma operação específica com as entradas e saídas de que o modelo necessita. O modelo escolhe qual a ferramenta a chamar. Um código determinístico valida cada pedido e aplica as permissões e regras de negócio antes de a chamada chegar ao serviço subjacente.

As instruções ajudam o modelo a escolher a operação correta, mas essa orientação é meramente consultiva. A validação e a aplicação das regras são executadas no código da ferramenta em cada chamada, criando um limite de segurança estável à medida que as instruções e os modelos mudam.

A conceção de ferramentas requer equilíbrio. Uma ferramenta demasiado abrangente pode apresentar demasiadas opções ao modelo, enquanto uma ferramenta demasiado restrita pode transformar uma tarefa numa longa cadeia de chamadas. O nosso servidor MCP partilhado disponibiliza agora mais de 60 ferramentas em fluxos de trabalho públicos e internos de agentes. Um novo agente seleciona as ferramentas de que necessita a partir dessa biblioteca, e as melhorias na validação, telemetria ou integração de serviços beneficiam todos os agentes que as utilizam.

Figura 3: As instruções influenciam o que o modelo solicita; o código da ferramenta MCP determina o que pode ser executado.

Pronto para produção por predefinição

A ADK forneceu-nos os elementos básicos para a criação de um agente, incluindo instruções, ferramentas, callbacks, sessões e integração de modelos. Os agentes da DoorDash também necessitavam de rastreio distribuído, acesso a modelos, estado duradouro e controlos de implementação. Sem uma camada partilhada, cada equipa de domínio teria de tomar essas decisões e gerir a infraestrutura resultante de forma independente.

Criámos módulos reutilizáveis com base no ADK para funcionalidades que devem funcionar da mesma forma em todos os agentes. As equipas de domínio continuam a escolher as suas instruções, ferramentas e configuração do modelo. A plataforma liga essas escolhas aos sistemas necessários para executar os agentes de forma fiável em produção.

O rastreio é um exemplo. Uma equipa ativa essa funcionalidade através da configuração, e o SDK partilhado propaga um ID de rastreio através de chamadas A2A, ferramentas MCP e serviços DoorDash a jusante. Os engenheiros podem acompanhar uma solicitação ao longo do Orchestrator, dos agentes de domínio e das chamadas às ferramentas, em vez de terem de reunir registos de sistemas separados. Os mesmos dados de rastreio servem de apoio à depuração e à avaliação. A disponibilização do rastreio através da plataforma poupa cerca de um mês de trabalho de observabilidade por cada lançamento de um novo agente.

O acesso aos modelos segue o mesmo princípio. As equipas de domínio escolhem quais os modelos que os seus agentes utilizam, enquanto a plataforma padroniza a forma como esses modelos são invocados, rastreados e protegidos por comportamentos de fallback. As equipas podem avaliar e adotar novos modelos sem terem de reescrever integrações específicas de cada fornecedor. Esse caminho comum permitiu as rápidas atualizações de modelos e as melhorias na latência descritas anteriormente.

Estado partilhado para conversas fiáveis

As primeiras tentativas de desenvolver produtos com agentes da DoorDash, em 2025, demonstraram a rapidez com que a experiência se deteriora quando o estado do sistema não é fiável. Um agente podia responder bem a um pedido, mas depois esquecer ou utilizar incorretamente informações de uma troca de mensagens anterior. Resolver esse problema exigiu mais do que apenas adicionar o histórico da conversa ao prompt.

O Ask DoorDash utiliza três tipos de estado. O estado de sessão acompanha a conversa ativa e o trabalho já concluído no âmbito dessa conversa. A memória conserva informações que podem ser úteis em conversas posteriores, tais como as preferências do utilizador. Os artefactos contêm resultados estruturados que os agentes criam e atualizam, incluindo listas de compras e cartões interativos. Cada um tem um período de validade e um padrão de acesso diferentes.

Os projetos anteriores de agentes do DoorDash implementavam estas funcionalidades de forma independente. Isso duplicava o trabalho de persistência e fazia com que a fiabilidade dependesse das escolhas de cada equipa. Centralizámos essas funcionalidades nos Serviços de Agentes Geridos, que fornecem APIs compatíveis com o ADK para sessões, memória e artefactos. Os agentes de domínio utilizam as mesmas interfaces sem terem de gerir os seus próprios sistemas com estado.

A centralização do estado continua a exigir regras de armazenamento e de ciclo de vida distintas para os dados de sessão, a memória de longo prazo e os artefactos. O Managed Agent Services mantém esses limites num único local. Cada domínio continua a decidir quais as informações que a sua experiência deve guardar e recuperar.

Os Serviços de Agentes Geridos e a arquitetura de memória são abordados com mais pormenor na Parte II.

Mantenha-se informado com as actualizações semanais

Subscreva o nosso blogue de Engenharia para receber actualizações regulares sobre todos os projectos mais interessantes em que a nossa equipa está a trabalhar

O que a plataforma alterou

O módulo «Reservas» constituiu um teste concreto da plataforma. A equipa reutilizou o fluxo de produção já em funcionamento para os módulos «Restaurante» e «Mercearia» e lançou o suporte para «Reservas» numa semana, cerca de 10 vezes mais rápido do que se tivesse de criar os agentes de domínio iniciais.

As equipas de domínio mantêm a responsabilidade pelo comportamento dos agentes e pelos critérios de qualidade. A plataforma fornece o conjunto de ferramentas de avaliação, as ferramentas MCP, o rastreio, o acesso aos modelos e os controlos de implementação, para que as melhorias nesses componentes cheguem a todos os agentes que os utilizam.

A infraestrutura partilhada aumenta o alcance dos erros. Uma falha pode afetar vários agentes, e uma abstração prematura pode obrigar produtos diferentes a assumirem o mesmo formato. Só padronizamos uma funcionalidade depois de vários domínios a necessitarem dela e de implementações separadas poderem criar problemas de fiabilidade ou operacionais.

Junte-se a nós

Se a criação de plataformas de agentes e de IA orientada para o utilizador em grande escala lhe parece interessante, consulte as nossas vagas abertas na área de engenharia em careersatdoordash.com.

Como criámos uma camada de avaliação que nos permite saber onde, porquê e em que medida devemos confiar num revisor de código proativo, e por que razão uma única métrica nunca poderia fazer isso.

Em poucas palavras

A DoorDash descreveu recentemente como criámos um agente de revisão de código em produção a quem os engenheiros realmente dão ouvidos. Esse artigo abordou as escolhas de produto e arquitetura subjacentes ao agente: um «lead scout», revisores aprofundados, contexto focado, uma abordagem que privilegia a precisão em detrimento da recuperação e um design independente do modelo. O DashBench é a nossa camada de medição subjacente a tudo isto. O DashBench reproduz PRs históricos e avalia se os sistemas revelam conclusões reais e passíveis de ação humana, em vez de se limitarem a produzir comentários plausíveis. Essa distinção é importante porque sinais convenientes, como a taxa de aceitação, o feedback positivo ou uma única pontuação agregada, podem fazer com que um revisor pareça útil, ao mesmo tempo que ocultam as suas falhas: ignorar problemas importantes, dar demasiada importância a comentários fáceis ou trocar a recuperação pela precisão de formas que as métricas do produto não revelam.

O resultado principal: no relatório de 105 casos, o revisor de código de produção da DoorDash (Claude Sonnet 4.6 «high scout» + Claude Opus 4.8 «high reviewer») identificou 504 resultados verdadeiros com uma taxa de recuperação ponderada de 53,6%, em comparação com 164 resultados verdadeiros e uma taxa de recuperação ponderada de 30,7% para uma linha de base GPT 5.5 «high» sem «scout». A visão mais ampla da combinação de modelos é mais interessante do que um único vencedor: o Kimi K2.6 como «scout» + Claude Fable 5 como «reviewer» liderou a taxa de recuperação ponderada e o F1, enquanto o Composer 2.5 como «scout» + GPT 5.5 «medium» como «reviewer» liderou a precisão ponderada, e as linhas de base de passagem única mantiveram-se muito mais económicas.

Figura 1: Compromisso ponderado entre precisão e recall
Figura 2: Compromisso entre F1 ponderado e custo.

Nota: As métricas ponderadas atribuem maior peso aos problemas de maior gravidade (crítico = 4, elevado = 2, médio = 1, baixo = 0,5).

Por que razão os sinais óbvios enganam

A forma mais tentadora de avaliar um revisor de código — e a principal métrica utilizada atualmente pela maioria das principais ferramentas de revisão de código — consiste em observar o que acontece em produção: os autores aceitam os seus comentários? Tomam medidas com base neles? Esse sinal é real. Mas assenta numa base de pressupostos frágeis e de informação incompleta. Na linguagem das matrizes de confusão, a aceitação preenche sempre apenas duas das quatro células: um comentário que o autor aceita é registado como um verdadeiro positivo (TP), e um que rejeita como um falso positivo (FP). Ambas as classificações pressupõem que a decisão humana é a verdade absoluta — que os engenheiros são infalíveis, ou pelo menos que se enganam com pouca frequência e de forma suficientemente aleatória para que os erros não tenham importância no conjunto. Como veremos, essa é uma aposta arriscada. 

As outras duas células ficam vazias, e esse é o verdadeiro custo. A aceitação não consegue registar falsos negativos (erros que o revisor não detetou) nem verdadeiros negativos (código limpo em que o silêncio era a resposta correta). Indica que algo aconteceu; não indica se a revisão estava correta, se o silêncio se justificava ou o que foi ignorado. O DashBench lida com isso de forma diferente: após a avaliação, os conjuntos de problemas reais não detetados contam como falsos negativos para a pontuação do benchmark, mesmo que a telemetria de aceitação em produção não os consiga observar.

O outro problema mencionado acima é que a aceitação humana é um sinal útil, mas não constitui a verdade absoluta. Os autores aceitam e rejeitam comentários por motivos relacionados com o produto e o fluxo de trabalho: o momento certo, a urgência das relações públicas, o contexto de responsabilidade, o grau de invasividade da correção ou se a questão já foi tratada de outra forma. Isso torna a aceitação uma telemetria valiosa do produto, mas, por si só, um indicador de referência fraco. Nas auditorias de desacordo, tanto a revisão humana como a verificação automática revelaram erros; o objetivo não era declarar que um dos lados estava certo, mas sim distinguir entre «foi aceite» e «era verdadeiro». A métrica mais acessível pode, ainda assim, apontar para o alvo errado.

É por isso que criámos o DashBench. Se nos baseássemos numa métrica conveniente para construir e melhorar um sistema do qual os nossos engenheiros agora dependem, estaríamos a otimizar com confiança em direção a um objetivo fundamentalmente falho. A solução não é uma única métrica melhor. É um benchmark que cruza vários sinais imperfeitos e nunca se baseia em nenhum deles como verdade absoluta. 

De uma arquitetura de produção a um ambiente limpo para realizar medições

O nosso revisor de produção utiliza uma arquitetura em fases que separa a deteção da verificação. Um revisor inicial analisa a alteração e assinala áreas suspeitas. Em seguida, os revisores aprofundados investigam as pistas mais sólidas, confirmam se cada preocupação se justifica e descartam as alegações que não resistem ao escrutínio. Este processo reflete a forma como os bons revisores humanos trabalham: formam intuições, concentram a atenção nas partes de risco da comparação e, só depois, validam antes de pedir ao autor que faça qualquer coisa.

Esse design proporciona-nos, por acaso, uma superfície de avaliação clara. Como o fluxo de trabalho é independente do modelo, cada componente é uma variável que podemos manter fixa ou alterar individualmente: o modelo de detecção, o modelo de revisão, a política de contexto, a política da ferramenta e o orçamento de tempo de execução. Podemos reproduzir os mesmos PRs «congelados» através do agente de produção, através de variantes em fases com diferentes atribuições de modelos e através de revisores simples de passagem única, e o que está a ser medido permanece constante.

Assim, a questão relevante deixa de ser «qual é o melhor modelo?». Passa a ser: para uma determinada arquitetura, política de contexto, política de ferramentas, orçamento de tempo de execução e combinação de modelos, que compromisso entre cobertura, precisão, custo e latência obtemos efetivamente, e onde é que isso falha? «Melhor» não tem sentido até se especificar melhor em que aspetos, em que casos e a que custo.

Como criámos o índice de referência

Figura 3: Pipeline de testes de desempenho do DashBench

O importante é o ciclo, não qualquer caixa em particular. Novos casos e alterações de modelo continuam a entrar no mesmo ciclo de repetição e pontuação, enquanto a revisão de divergências mantém a fiabilidade do benchmark quando o feedback humano, o comportamento de produção e o julgamento do agente não coincidem. Nessa fase, inspecionamos manualmente as evidências, decidimos se a conclusão é válida e reintroduzimos os casos resolvidos na calibração do juiz. O próprio juiz é baseado em LLM, mas o DashBench trata-o como um sinal calibrado, e não como a verdade absoluta.

Os casos.

O DashBench partiu de cerca de 1 000 candidatos a PR em bruto e foi selecionado de forma a incluir casos que colocam à prova diferentes comportamentos de revisão, tais como diferenças complexas, históricos de revisão confusos e resultados variados em termos de gravidade. O artigo utiliza o relatório válido de 105 casos para analisar sistemas em fases versus sistemas de passagem única, as opções do modelo de explorador/revisor e a qualidade ponderada pela gravidade numa fatia de avaliação consistente.

  • Relatórios de imprensa históricos com conclusões reais das revisões e contexto suficiente para reproduzir fielmente o processo de revisão.
  • PRs benignas com resultados reais praticamente nulos, pelo que o critério de referência pode avaliar a moderação e detetar falsos positivos; um revisor que insista demasiado no código limpo constitui, por si só, um modo de falha.
  • PRs que foram posteriormente revertidos ou corrigidos através de hotfixes, para que o teste de desempenho possa verificar se um sistema deteta regressões genuínas ao nível do código antes da fusão.

Os rótulos. 

É aqui que a maioria dos benchmarks toma um atalho que nós nos recusámos a seguir. A preparação das etiquetas exigiu mais do que apenas importar o feedback humano. Pedimos aos engenheiros que escreveram os PRs para anotarem as descobertas candidatas e, em seguida, comparámos três fontes entre si: as anotações humanas, as descobertas candidatas originais e um avaliador automático. O feedback humano era valioso, mas frequentemente errado: os revisores deixavam escapar problemas válidos, aceitavam alegações fracas ou interpretavam o contexto de um PR antigo de forma diferente daquela que o revisor seguinte faria. Nos casos em que as três fontes discordavam, revíamos manualmente as evidências e resolvíamos a questão, e esses casos resolvidos tornavam-se dados de calibração para o avaliador.

O resultado é um ponto de referência cuja verdade de referência não assenta numa única fonte falível. O julgamento humano, o feedback da produção e a avaliação por parte dos agentes contribuem, cada um à sua maneira; nenhum deles é considerado infalível. 

O ambiente de execução. 

Numa comparação, cada configuração é executada com base no mesmo conjunto de casos fixo, na mesma sequência de contexto, na mesma seleção de prompts e pacotes de competências, no mesmo contrato de resultados normalizados e no mesmo fluxo de avaliação. O conjunto de testes faz parte do que estamos a medir: os perfis diferem no tipo de executor, nos modelos de explorador/revisor, na interface da ferramenta, na mecânica do contexto e nos limites do fornecedor. Alguns são executados a partir de um repositório preparado com ferramentas do tipo «read/grep», outros recebem um contexto de repositório delimitado no prompt e outros ainda desativam as etapas de pesquisa ou avaliação na execução de revisão. Custo, latência, tempos de espera, novas tentativas e falhas são registados nos artefactos da execução e nos resumos do conjunto de testes, em vez de serem tratados como constantes controladas. O objetivo é a repetibilidade: quando um valor muda, queremos saber se mudou porque o modelo ou o conjunto de testes mudou, e não porque a avaliação se desviou.

O relatório: O que o DashBench realmente mostra

Utilizamos o DashBench para responder a quatro perguntas que a telemetria de produção, por si só, não consegue esclarecer. Em primeiro lugar, será que a abordagem por fases proporciona efetivamente uma maior cobertura? Em segundo lugar, quais são as opções de modelos de «scout»/revisor que alteram a fronteira entre custo, precisão e recall? Em terceiro lugar, as classificações gerais mantêm-se quando dividimos os resultados por gravidade? Em quarto lugar, em que situações é que as configurações de «scout»/revisor em fases superam revisores de passagem única mais fortes e o que é que sacrificam para o conseguir?

Vantagens da encenação

A simulação garante cobertura, e o preço é visível. No relatório de 105 casos, o revisor de código de produção da DoorDash (Claude Sonnet 4.6 «high scout» + Claude Opus 4.8 «high reviewer») identificou 504 resultados reais com uma taxa de recuperação ponderada de 53,6%, em comparação com 164 resultados reais e uma taxa de recuperação ponderada de 30,7% para a linha de base do GPT 5.5 «high» sem «scout». A precisão ponderada manteve-se em valores semelhantes, 87,0% contra 84,1%, mas o revisor de produção teve um custo mais elevado por PR e demorou mais tempo. É exatamente este o tipo de resultado que queremos que o DashBench revele: não um troféu, mas uma análise ponderada das vantagens e desvantagens.

SistemaConclusões reaisPrecisão ponderadaRecuperação ponderadaCusto / Relações Públicas
Revisor de código de produção da DoorDash50487.0%53.6%$3.91
Sem scout + Avaliador de alto nível do GPT 5.516484.1%30.7%$0.75
Sem scout + Claude Opus 4,8 – avaliador com pontuação elevada11589.8%20.2%$0.65

Escolha do modelo de explorador/avaliador

É na comparação entre combinações de modelos que o benchmark se revela mais útil: trocamos quais modelos atuam como «scouts» e quais como «reviewers», mantendo o benchmark fixo. Nenhuma configuração domina todos os eixos. A combinação Kimi K2.6 como «scout» + Claude Fable 5 como «reviewer» apresentou o recall ponderado e o F1 mais elevados no subconjunto válido de 105 casos, com 65,2% de recall ponderado e 75,3% de F1 ponderado. A combinação do explorador Composer 2.5 com o revisor médio GPT 5.5 apresentou a precisão ponderada mais elevada, de 92,2%, mas com um recall significativamente inferior. As linhas de base sem explorador foram mais económicas, enquanto a combinação do explorador Kimi K2.6 com o revisor de alto nível Claude Opus 4.8 constituiu uma alternativa em fases de menor custo, mas com um recall significativamente inferior.

ConfiguraçãoConclusões reaisPrecisão ponderadaRecuperação ponderadaF1 ponderadoCusto / Relações Públicas
Kimi K2.6 (explorador) + Claude Fable 5 (crítico)53789.2%65.2%75.3%$3.81
Claude Sonnet 4.6 – explorador de alto nível + Claude Opus 4.8 – crítico de alto nível50487.0%53.6%66.3%$3.91
Kimi K2.6 scout + Claude Opus 4.8, com muitas avaliações positivas39682.3%45.8%58.9%$2.35
Claude Sonnet 5 – explorador de alto nível + Claude Sonnet 5 – avaliador de alto nível32177.3%40.1%52.8%$6.55
Claude Sonnet 5 – explorador de alto nível + Claude Opus 4.8 – crítico de alto nível22680.8%32.9%46.8%$5.06
GPT 5.5 (escoteiro de nível médio) + GPT 5.5 (revisor de nível elevado)27691.5%19.9%32.6%$5.95
Composer 2.5 (scout) + GPT 5.5 (revisor de nível elevado)26791.1%19.6%32.2%$4.68
Composer 2.5 (scout) + GPT 5.5 (revisor médio)24692.2%18.0%30.1%$3.53
Sem scout + Avaliador de alto nível do GPT 5.516484.1%30.7%45.0%$0.75
Sem scout + Claude Opus 4,8 – avaliador com pontuação elevada11589.8%20.2%33.0%$0.65

Impacto da gravidade dos resultados na avaliação

A gravidade volta a alterar o panorama. No subconjunto válido de 105 casos, o conjunto de resultados reais avaliados contém 40 clusters críticos, 136 de gravidade elevada, 271 de gravidade média e 385 de gravidade baixa. A combinação Kimi K2.6 (scout) + Claude Fable 5 (revisor) apresentou melhor desempenho na cobertura de casos críticos, de gravidade elevada e média, enquanto a combinação Claude Sonnet 4.6 (scout) + Claude Opus 4.8 (revisor) cobriu um pouco mais a cauda de gravidade baixa. A linha «GPT 5.5 high» sem «scout» era económica e continuava a ser útil em problemas de gravidade elevada, mas mais fraca na cobertura global. É por isso que existe a pontuação ponderada: atribui mais peso às falhas de gravidade crítica e elevada do que às de gravidade baixa, permitindo-nos, ao mesmo tempo, inspecionar a divisão completa por gravidade.

Figura 4: Recolha por gravidade.

A visualização da gravidade mostra por que razão uma única pontuação não é suficiente: configurações que, em termos gerais, parecem semelhantes podem apresentar problemas de natureza muito diferente.

Influência da qualidade e da dimensão do modelo nos resultados da avaliação comparativa

Os dados da combinação de modelos confirmam o mesmo em números: o Kimi K2.6 (scout) + Claude Fable 5 (reviewer) lidera em recall ponderado e F1; o Composer 2.5 (scout) + GPT 5.5 (reviewer médio) lidera em precisão ponderada; e as linhas de base de passagem única sem scout mantêm-se mais económicas, embora sacrifiquem a cobertura. Uma configuração de staging específica não torna um modelo mais fraco, por magia, o melhor em tudo; altera sim a natureza do compromisso. Os «scouts» melhoram a amplitude quando o «reviewer» consegue verificar de forma agressiva. Configurações mais rigorosas do «reviewer» melhoram a precisão quando o objetivo empresarial é reduzir o ruído. As linhas de base de passagem única sem «scout» constituem um ponto de referência útil e de baixo custo, mas deixam a cobertura em segundo plano. O resultado não é que um modelo vença. O resultado é que nenhuma configuração isolada domina, e poder afirmar exatamente isso é que importa.

Lições retiradas da análise comparativa da avaliação das relações públicas

A confiança tem de ser conquistada. Nenhum sinal, por si só, constitui uma fonte fiável de verdade para uma avaliação comparativa no mundo real. As classificações humanas, a aceitação na produção e o julgamento autónomo contribuíram, cada um à sua maneira, e cada um deles errou com frequência suficiente para que considerar qualquer um deles como a «verdade absoluta» tivesse sub-repticiamente distorcido as pontuações. A qualidade da avaliação comparativa reside precisamente na sua recusa em confiar exclusivamente em qualquer dado falível.

Os seres humanos não conseguem lidar com grandes volumes. A capacidade de atenção humana para a rotulagem diminui rapidamente em tarefas complexas, e ainda mais rapidamente em muitas delas, mesmo quando quem faz a rotulagem é o próprio engenheiro que escreveu ou reviu o PR original. A avaliação por IA é o que torna a rotulagem a esta escala sequer viável, mas apenas dentro das restrições apresentadas nas duas próximas lições.

A variância é uma característica, não um defeito. Os LLMs são não determinísticos, pelo que várias execuções do mesmo agente revelam resultados válidos adicionais, o que significa que uma única execução subestima a cobertura real de um agente, sendo necessário executá-lo repetidamente e agregar os resultados para o avaliar de forma honesta. A mesma estocasticidade aplica-se ao avaliador: a correspondência determinística é estável, mas não capta a equivalência semântica, enquanto os avaliadores baseados em LLMs raciocinam de forma mais rica, mas necessitam de calibração, conjuntos de auditoria e critérios de avaliação estáveis para se manterem fiáveis. Todo o conjunto de medições é estocástico; o trabalho consiste em projetar tendo isso em conta, em vez de fingir que não existe.

Quando uma métrica não funciona, recorra a várias. Uma única pontuação é, por definição, enganadora. A precisão ponderada, o recall ponderado, o F1 ponderado, os resultados não reais, o recall elevado/crítico, a latência e o custo variam de forma independente; um sistema pode ter bons resultados numa métrica e maus noutra na mesma execução, pelo que «melhor» não tem sentido até se especificar «melhor em que aspeto».

Mantenha-se informado com as actualizações semanais

Subscreva o nosso blogue de Engenharia para receber actualizações regulares sobre todos os projectos mais interessantes em que a nossa equipa está a trabalhar

O que se segue

O DashBench existe para que possamos continuar a melhorar o revisor com dados concretos, em vez de meras anedotas. O objetivo nunca foi criar uma tabela de classificação estática; trata-se de um ciclo de feedback em que todas as alterações significativas ao modelo, prompt, contexto, utilização de ferramentas, fluxo de trabalho ou orçamento de tempo de execução são testadas nos mesmos casos reais de revisão de PR antes de chegarem a um engenheiro.

A próxima etapa consiste numa análise comparativa contínua:

  • Os novos modelos e conjuntos de testes são rapidamente incorporados no benchmark à medida que são lançados, incluindo os conjuntos de testes de agentes adicionais que planeámos para esta fase.
  • Os casos de PR obsoletos são retirados do conjunto de dados, enquanto novos casos são adicionados continuamente, pelo que o benchmark acompanha a base de código em vez de se afastar dela.
  • Estamos a passar de um único juiz para um júri agênico, a fim de mitigar o enviesamento entre os modelos de juízes individuais.
  • Estamos a comparar com soluções externas de revisão de código, e não apenas com variantes internas.

E estamos a começar a avaliar agentes de programação, e não apenas revisores, numa base de código empresarial real, com tarefas e funcionalidades reais. Falaremos mais sobre isso mais tarde.

O resumo sincero é este: tornar um sistema de IA útil é apenas metade do trabalho. A parte mais difícil é saber onde falha, por que falha e se a alteração seguinte o melhorou ou apenas o tornou diferente. A maior parte da área ainda está a medir a unidade errada com o número errado. O DashBench é a nossa tentativa de medir o trabalho que realmente importa: resultados reais, em PRs reais, com as compensações visíveis. Se esse é o tipo de problema em que queres trabalhar, gostaríamos de falar contigo.


Apêndice

Espaço para detalhes que reforçam o artigo sem tornar o texto mais lento. A secção «Relatório» mantém o leitor a par das vantagens e desvantagens; esta secção mantém o registo de auditoria.

A. Ambiente de execução (especificações completas). Numa comparação, cada configuração é executada no mesmo conjunto de dados segmentado e produz o mesmo contrato de resultados estruturado: gravidade, evidência e referências de ficheiros. O próprio ambiente de teste faz parte do que o DashBench avalia, pelo que os perfis podem diferir no tipo de executor, na divisão entre explorador e revisor, na escolha do modelo, na interface da ferramenta, na mecânica do contexto e nos limites do fornecedor. A pontuação utiliza então o mesmo caminho de correspondência para essa comparação, com correspondência determinística sempre que possível e avaliação discrecional quando for necessária uma correspondência semântica. O objetivo não é eliminar as diferenças entre os sistemas, mas sim torná-las suficientemente explícitas para que o custo, a latência e a qualidade variem por razões interpretáveis.

B. Dimensões do conjunto de dados e do conjunto de avaliação. O DashBench partiu de cerca de 1 000 candidatos a PR em bruto, tendo depois restringido a análise aos casos que podiam ser reproduzidos e avaliados. A publicação utiliza o relatório válido de 105 casos para a análise principal da combinação de modelos e da comparação entre a abordagem em fases e a de passagem única. O denominador de gravidade nessa seleção de 105 casos é a união dos agrupamentos de resultados reais: 40 críticos, 136 elevados, 271 médios e 385 baixos. Trata-se de agrupamentos de resultados, não de contagens de PR; um PR pode contribuir para mais do que um agrupamento.

C. Métricas completas de configuração. O corpo do texto apresenta a versão resumida, uma vez que é a versão mais legível. As métricas completas encontram-se abaixo, para efeitos de auditoria, divididas de forma a caberem na página.

MétricaRevisor de código de produção da DoorDashSem scout + Avaliador de alto nível do GPT 5.5
ConfiguraçãoClaude Sonnet 4.6 – explorador de alto nível + Claude Opus 4.8 – crítico de alto nívelRevisor único
Resultados brutos611200
Conclusões reais504164
Precisão ponderada87.0%84.1%
Recuperação ponderada53.6%30.7%
F1 ponderado66.3%45.0%
Recuperação elevada/crítica52.8%51.7%
Custo / Relações Públicas$3.91$0.75
Custo / conclusão real$0.82$0.48
Latência de revisão / PR725.0s170.3s
AdvertênciaCusto e latência mais elevados, mas cobertura mais amplaCusto e latência mais baixos, mas uma taxa de recuperação global muito mais baixa

ConfiguraçãoConclusõesQualidadeCusto / latência
Kimi K2.6 (explorador) + Claude Fable 5 (crítico)669 brutos
537 reais
132 não reais
89,2% de precisão
65,2% de recall
75,3% de F1
3,81 $ / PR
0,75 $ / real
589,3 s / PR
Melhor recall ponderado/F1
Claude Sonnet 4.6 – explorador de alto nível + Claude Opus 4.8 – crítico de alto nível611 bruto
504 real
107 não real
87,0% de precisão
53,6% de recall
66,3% de F1
3,91 $ / PR
0,82 $ / real
725,0s / PR
Kimi K2.6 scout + Claude Opus 4.8, com muitas avaliações positivas574 brutos
396 reais
178 não reais
82,3% de precisão
45,8% de recall
58,9% de F1
2,35 $ / PR
0,62 $ / real
263,9 s / PR
A configuração em etapas mais rápida registada
Claude Sonnet 5 – explorador de alto nível + Claude Sonnet 5 – avaliador de alto nível509 em bruto
321 reais
187 não reais
1 indeterminado
77,3% de precisão
40,1% de recall
52,8% de F1
6,55 $ / PR
2,14 $ / real
657,9s / PR
Claude Sonnet 5 – explorador de alto nível + Claude Opus 4.8 – crítico de alto nível327 bruto
226 real
100 não real
1 incerto
80,8% de precisão
32,9% de recall
46,8% de F1
5,06 $ / PR
2,35 $ / real
565,3s / PR
GPT 5.5 (escoteiro de nível médio) + GPT 5.5 (revisor de nível elevado)332 brutos
276 reais
56 não reais
91,5% de precisão
19,9% de recall
32,6% de F1
5,95 $ / PR
2,26 $ / real
619,5s / PR
Composer 2.5 (scout) + GPT 5.5 (revisor de nível elevado)324 brutos
267 reais
57 não reais
91,1% de precisão
19,6% de recall
32,2% de F1
4,68 $ / PR
1,84 $ / real
539,2s / PR
Composer 2.5 (scout) + GPT 5.5 (revisor médio)285 brutos
246 reais
39 não reais
92,2% de precisão
18,0% de recall
30,1% de F1
3,53 $ / PR
1,50 $ / real
429,4s / PR
Melhor precisão ponderada
Sem scout + Avaliador de alto nível do GPT 5.5200 brutos
164 reais
36 não reais
84,1% de precisão
30,7% de recall
45,0% de F1
0,75 $ / PR
0,48 $ / real
170,3 s / PR
Sem scout + Claude Opus 4,8 – avaliador com pontuação elevada134 em bruto
115 reais
19 não reais
89,8% de precisão
20,2% de recall
33,0% de F1
0,65 $ / PR
0,60 $ / real
112,8s / PR

D. Discriminação por gravidade (completa). A perspetiva da gravidade é a razão mais evidente para a existência da métrica principal ponderada. As falhas críticas e de gravidade elevada não são intercambiáveis com as falhas de baixa gravidade, e as configurações variam consoante o nível.

Recolha por grau de gravidade

GravidadeAgrupamentos reais de uniãoKimi K2.6 + Fable 5Sonnet 4.6 high + Opus 4.8 highKimi K2.6 + Opus 4.8 (alta)GPT 5.5 alto
Crítico4080.0%62.5%72.5%37.5%
Elevado13677.9%50.0%46.3%55.9%
Médio27156.8%53.5%43.2%20.3%
Baixo38546.8%51.4%26.8%4.2%

Precisão em função da gravidade

GravidadeKimi K2.6 + Fable 5Sonnet 4.6 high + Opus 4.8 highKimi K2.6+ Opus 4.8 altoGPT 5.5 alto
Crítico100.0%100.0%100.0%n/a
Elevado96.0%95.6%94.9%86.9%
Médio94.2%87.8%86.8%80.9%
Baixo65.3%76.8%49.7%70.4%

As intenções das tarefas são de múltiplas etiquetas, pelo que a soma das contagens é superior a 105.

Intenções de tarefa

Objetivo da tarefaContagemPercentagem de casos
testes9085.7%
config_build8581.0%
característica4139.0%
api_schema3331.4%
docs_kb2019.0%
ferramentas_de_dependências1918.1%
correção de erros1817.1%
modelo_de_dados1514.3%
refactor_cleanup1514.3%
ui109.5%
rollout_guard98.6%
segurança_privacidade87.6%
observabilidade76.7%
desempenho65.7%
mudança_de_comportamento11.0%

Alterar o tamanho do PR

Classe de dimensão PRContagemPercentagem de casos
grande3634.3%
médio3634.3%
pequeno3331.4%

Verificabilidade

VerificabilidadeContagemPercentagem de casos
médio5451.4%
forte3836.2%
fraco1312.4%

Áreas de produtos

São apresentados os 20 principais domínios.

Área de produtosContagemPercentagem de casos
feed_do_consumidor1817.1%
material_de_campanha65.7%
logística_mão-de-obra54.8%
cesto de compras54.8%
funil_de_apoio_de_apoio43.8%
risco_de_fraude_básico32.9%
logística_execução de encomendas32.9%
money_payin32.9%
conteúdo_de_descoberta_do_consumidor21.9%
helios21.9%
merchant_mdh21.9%
encomendas_do_comerciante21.9%
apoio_aos_comerciantes21.9%
serviço_de_gestão_de_utilizadores_comerciais21.9%
platform_pretzel21.9%
público21.9%
repo_config21.9%
suporte_automatização_agente_IA21.9%
voz_de_apoio21.9%
ferramentas21.9%

Impacto

As etiquetas de impacto são etiquetas múltiplas, pelo que o total das contagens é superior a 105.

ImpactoContagemPercentagem de casos
de contacto com o cliente3432.4%
merchant_ops3331.4%
fiabilidade_da_infraestrutura3028.6%
logística2221.0%
integridade_dos_dados1817.1%
conhecimentos_de_programador1615.2%
desconhecido1615.2%
qualidade do teste98.6%
segurança_privacidade87.6%
dinheiro76.7%

A DoorDash presta serviços a um conjunto vasto e diversificado de comerciantes, sendo que cada restaurante, ementa e prato se apresenta de uma forma única. Um catálogo de comida de alta qualidade constitui a espinha dorsal da pesquisa dos clientes e da experiência de personalização, representando um fator-chave para o sucesso dos restaurantes. Ao contrário dos catálogos padronizados, a comida é profundamente contextual, rica em termos culturais e altamente não padronizada; o mesmo prato pode ser descrito de inúmeras formas, enquanto pratos totalmente diferentes podem partilhar nomes, descrições ou imagens semelhantes.  Acrescente-se a isto que, à escala da DoorDash, existem milhões de itens únicos e atualizações constantes dos menus. Esta variabilidade e volume tornam extremamente difícil gerar metadados fiáveis através de abordagens tradicionais. 

Para resolver esta questão, criámos uma plataforma de metadados de restaurantes baseada em IA. A nossa plataforma deduz atributos ao nível dos pratos e dos estabelecimentos — por exemplo, se um prato é picante ou se a cozinha de um restaurante é chinesa — utilizando sinais multimodais provenientes de texto, imagens e pesquisas abrangentes na Internet. Para criar metadados fiáveis e precisos em grande escala, desenvolvemos várias inovações fundamentais no complexo sistema da DoorDash, incluindo: 

  • Um sistema de avaliação baseado num modelo de linguagem de grande dimensão (LLM) para uma avaliação de alta qualidade, que aumentou a precisão da anotação em cerca de 20 % em comparação com os revisores humanos habituais.
  • Agentes de otimização contextual para melhorar iterativamente os prompts em poucos minutos, aumentando a precisão do modelo em mais de 20 % e evitando, ao mesmo tempo, a ineficiência dos prompts criados manualmente e subótimos. Este ciclo acelerou o desenvolvimento dos prompts dez vezes.
  • A computação distribuída permite a inferência de LLM em grande escala, reduzindo o tempo de preenchimento de mais de um mês para apenas alguns dias, tornando operacionalmente viável a geração de milhões de itens.
  • Anotação orientada por IA para gerar dados de treino, o que permitiu o ajuste fino para atingir a qualidade dos LLM de ponta a 10 % do custo de inferência, sem qualquer esforço de anotação humana.

Esta plataforma de metadados permite-nos implementar com sucesso a IA generativa de forma fiável e económica em grande escala, melhorando o nosso fluxo de trabalho de engenharia e a experiência do consumidor da DoorDash. 

Visão geral de alto nível do fluxo

Conforme ilustrado na Figura 1, o nosso processo começa com a recolha de atualizações do menu e a deduplicação, para minimizar os custos de inferência. Introduzimos esses itens em geradores de IA para produzir metadados, que são submetidos a uma validação estrutural imediata para deteção de erros e novas tentativas. Monitorizamos continuamente a qualidade das previsões geradas através de um júri de LLM; o resultado da avaliação é também utilizado para a engenharia de contexto, com vista a melhorar a qualidade da geração. Além disso, disponibilizamos um mecanismo de substituição por parte do comerciante que permite aos proprietários das empresas validar ou corrigir atributos.

Figura 1: Esta visão geral do fluxo de geração de metadados de refeições da DoorDash inclui atualizações do menu e a eliminação de duplicados através da geração por IA, avaliação e alterações por parte dos comerciantes.

Inovações técnicas

A recolha, rotulagem, treino e avaliação tradicionais de dados são excessivamente lentos e dispendiosos, o que torna difícil gerar e extrair metadados de alta qualidade na escala exigida pela DoorDash. Para resolver esta questão, desenvolvemos um sistema que utiliza tanto modelos de linguagem multimodais como pequenos modelos de linguagem (SLMs) treinados, de modo a alcançar uma geração de alta qualidade e baixa latência a um custo razoável. O nosso sistema «LLM Jury», cuidadosamente concebido, permite uma avaliação fiável em grande escala, a otimização contínua do contexto a partir de sinais reais de falha e a geração automatizada de dados rotulados de alta qualidade, de modo a acelerar o aperfeiçoamento dos modelos e a formação interna.

Avaliações eficientes e de alta qualidade com júris de LLM

A validação das etiquetas geradas através da rotulagem humana é impraticável na produção em grande escala. Apenas um pequeno grupo de especialistas na área consegue aplicar etiquetas de forma a corresponder à maneira como os clientes tomam efetivamente as suas decisões de encomenda e são ainda menos os especialistas que compreendem de forma fiável as nuances entre as diferentes cozinhas e a linguagem dos menus — por exemplo, distinguir pratos nepaleses dos do norte da Índia, ou interpretar se «ao estilo de Sichuan» implica um perfil de picante. Consequentemente, alargar a validação humana para abranger milhões de itens torna-se proibitivamente dispendioso e operacionalmente impraticável; a avaliação tradicional simplesmente não funciona para a geração contínua de metadados em grande volume.

O nosso sistema automatizado de avaliação de consenso baseado em LLM — os «júris LLM» — substitui a validação humana, que é lenta, dispendiosa e inconsistente. Conforme ilustrado na Figura 2, inclui as seguintes etapas:

  • Avaliação de LLM por consenso: vários avaliadores de LLM de grande qualidade avaliam, de forma independente, cada etiqueta proposta, em vez de se basearem num único modelo ou num único avaliador humano.
  • Votação e agregação: Cada avaliador apresenta um veredicto e uma justificação; os votos são agregados numa única decisão consensual.
  • Verificação ao nível da etiqueta: Os avaliadores validam cada etiqueta individualmente — por exemplo, «proteína», «preparação» ou «saúde» —, em vez de avaliarem o item como um todo. As etiquetas verificadas são guardadas e utilizadas na base de dados.

Figura 2: O sistema de avaliação por júri de LLM utiliza vários avaliadores de LLM de alto desempenho para avaliar as etiquetas propostas de forma independente. Agregamos os votos e verificamos as etiquetas individualmente.

Constatámos que as etiquetas consensuais do LLM eram cerca de 20 % mais precisas do que as etiquetas típicas anotadas por humanos. O sucesso da nossa estrutura de avaliação automatizada foi fundamental para automatizar todo o sistema de geração de metadados.

Otimização automática do contexto inspirada na aprendizagem por reforço 

O nosso sistema utiliza modelos de linguagem baseados em visão para melhorar a qualidade e a eficiência da geração de metadados alimentares. Embora possa ser fácil fornecer contexto a um prompt para gerar algumas etiquetas, gerar etiquetas altamente precisas em grande escala é muito mais difícil e não pode ser conseguido com um único prompt. Mesmo com engenheiros altamente qualificados, a engenharia manual de contexto é lenta, frágil e imprevisível. Pequenas alterações na formulação, que parecem equivalentes para os humanos, podem levar a comportamentos muito diferentes do modelo, e lidar com casos extremos requer repetidos processos de tentativa e erro. À medida que surgem novos padrões de itens e casos extremos, manter a qualidade dos prompts torna-se um esforço manual contínuo e impossível de escalar.

Figura 3: O ciclo de otimização contextual utiliza sinais de falha provenientes de conjuntos de dados de avaliação de alta qualidade para propor e testar alterações nas instruções, melhorando de forma iterativa a qualidade do modelo.

Inspirados pela aprendizagem por reforço, desenvolvemos um ciclo autónomo, ilustrado na Figura 3, que resultou num aumento de dez vezes na velocidade de desenvolvimento do contexto do prompt. Definimos a recompensa da tarefa com base no desempenho do modelo num conjunto de dados de avaliação de alta qualidade. Um agente de ajuste identifica onde o prompt atual apresenta um desempenho inferior e utiliza esses sinais de falha para propor um contexto melhor para o modelo. Em cada etapa, são geradas métricas utilizando um conjunto de dados de avaliação de alta qualidade. Estas funcionam como os nossos parâmetros de segurança, garantindo que o sistema melhora sempre a precisão e a recuperação globais. Otimizamos o próprio prompt, em vez de atualizarmos os pesos do modelo, tornando o ciclo muito mais rápido e económico de executar.

Optámos por esta abordagem orientada por sinais de falha em vez de métodos evolutivos baseados em populações, como o algoritmo GEPA, que mantêm uma população de prompts candidatos e recorrem a operadores de mutação e cruzamento para explorar o espaço de prompts. Em vez de atribuir pontuações cegamente a muitas variantes de prompts por geração, o nosso agente analisa diretamente os casos de falha e propõe alterações específicas às regras. Isto torna cada iteração orientada para um objetivo específico, em vez de probabilística, e requer menos rondas de avaliação, sem necessidade de hiperparâmetros de população que exijam afinação.

Algumas lições importantes que gostaríamos de partilhar:

  • A qualidade dos dados é fundamental para esta tarefa: o conjunto de dados de avaliação utilizado para classificar os candidatos a prompts determina diretamente a direção da otimização. Exemplos de baixa qualidade ou mal rotulados levam o agente a perseguir ruído em vez do sinal real, resultando em prompts de qualidade inferior ou instáveis.
  • Os casos de falha contêm mais informação do que os de sucesso: ao longo do desenvolvimento, testámos diferentes combinações de casos de falha e de sucesso. Concluímos que atribuir maior peso aos casos de falha foi a abordagem mais eficaz para o nosso caso de utilização.
  • A otimização dos prompts é tão rápida quanto a otimização dos pesos do modelo: a otimização dos prompts segue a mesma dinâmica de convergência que o treino do modelo; uma IA consegue concluir em horas o que um ser humano levaria dias ou semanas a fazer. 

Esta abordagem transforma a engenharia de contexto de uma tarefa ad hoc, conduzida por pessoas, num processo de otimização escalável e mensurável, que acompanha a evolução dos dados e dos casos de utilização. Verificámos que a precisão nos nossos casos aumentou mais de 20% num conjunto de dados de avaliação de validação.

Anotação de dados orientada por IA para acelerar a recolha de dados de treino

A geração de metadados à escala da DoorDash exige tanto precisão como eficiência. Os modelos de linguagem de grande escala (LLMs) disponíveis no mercado são, muitas vezes, imprecisos ou têm custos proibitivos; por isso, parte do nosso sistema de IA depende de modelos altamente especializados e ajustados. O treino desses modelos, no entanto, requer anotações em milhares de etiquetas em milhares de milhões de entidades do catálogo, o que torna a rotulagem de dados uma das maiores limitações no ciclo de desenvolvimento. Num fluxo de trabalho tradicional, a produção desse volume de dados de treino e avaliação depende fortemente da anotação humana, tornando a iteração do modelo lenta, dispendiosa e difícil de escalar.

Figura 4: O nosso sistema de anotação baseado em IA utiliza agentes dedicados à geração e à avaliação para criar e validar, de forma eficiente, rótulos de alta qualidade destinados ao treino de modelos especializados e otimizados.

Para resolver esta questão, criámos um sistema de anotação de dados baseado em IA, apresentado na Figura 4, que gera e valida rótulos de alta qualidade. Desenvolvemos um conjunto semelhante de agentes de otimização, geração e avaliação automática de contexto, especificamente para tarefas de anotação. Os nossos modelos pequenos e aperfeiçoados reduziram os custos de inferência em aproximadamente 90 % em comparação com os LLMs, ao mesmo tempo que alcançaram um desempenho comparável.

Otimização da inferência em grande escala de modelos LLM

Na escala da DoorDash, milhões de itens de menu únicos, milhares de milhões de opções de menu e centenas de milhares de atualizações diárias exigem uma atualização contínua dos metadados. Recorrer a chamadas de API síncronas, item a item, para preenchimento retroativo completo demoraria semanas, tornando as atualizações diárias impraticáveis e, ao mesmo tempo, aumentando os custos de infraestrutura e dos modelos. 

Figura 5: O nosso pipeline de inferência de LLM distribuído utiliza a deduplicação, a distribuição do Spark, o processamento em lotes e o remapeamento de resultados para transformar a geração em grande escala de um gargalo lento num processo eficiente e de elevado rendimento.

Para dar resposta a estes desafios, concebemos um pipeline de inferência de LLM distribuído, apresentado na Figura 5, com o objetivo de eliminar a computação redundante e maximizar o débito. A nossa abordagem assenta em quatro mecanismos-chave:

  • Desduplicação: Muitos comerciantes partilham nomes e descrições idênticos de artigos; um processamento simplista enviaria repetidamente dados idênticos para o modelo. Realizamos a desduplicação através de correspondências exatas de características, evitando chamadas redundantes ao modelo.
  • Distribuição no Spark: Dividimos os dados únicos restantes em blocos e distribuímo-los por um cluster de workers do Spark para processamento paralelo.
  • Processamento em lote: Utilizamos APIs de LLM em lote para enviar cargas agrupadas ao modelo, maximizando o rendimento e a eficiência em termos de custos. No caso de modelos treinados, fragmentamos os dados, o que nos permite executar novamente o processo em várias GPUs.
  • Remapeamento de resultados: Após o processamento, remapeamos os resultados do modelo de volta às entidades originais, de modo a preservar a integridade dos dados.

Em conjunto, estas otimizações transformam a geração de metadados em grande escala de um estrangulamento lento e dispendioso num fluxo de trabalho altamente eficiente, escalável e económico, reduzindo o tempo de preenchimento retroativo de mais de um mês para apenas alguns dias.

Promover uma melhor experiência do cliente 

Os nossos metadados servem de base para inúmeras aplicações a jusante em toda a plataforma DoorDash. Ao transformar o texto não estruturado dos menus em atributos precisos e estruturados, desbloqueamos muitas novas funcionalidades, incluindo o reforço da pesquisa e descoberta por parte dos clientes, a possibilidade de filtrar pratos relevantes, a personalização e a geração de métricas para análise, conforme ilustrado na Figura 6:

Figura 6: Os metadados estruturados constituem a camada fundamental para as aplicações posteriores da DoorDash, permitindo a personalização para o cliente, a filtragem e uma pesquisa melhorada.

Conclusão

A plataforma de IA de metadados representa uma mudança fundamental na forma como a DoorDash compreende os artigos e as lojas na sua plataforma. Através da IA, desenvolvemos uma compreensão semântica profunda de cada artigo e loja, transformando dados de entrada inconsistentes e não estruturados em atributos ricos, precisos e estruturados. Ao combinar um pipeline de geração de dados distribuído em grande escala com uma avaliação automatizada rigorosa e salvaguardas que envolvem a intervenção humana, demonstrámos que a IA generativa pode ser implementada de forma fiável e económica em ambientes de produção de grande volume. A infraestrutura de metadados resultante não se limita a alimentar as funcionalidades atuais de pesquisa e descoberta; estabelece uma base de dados robusta e de alta qualidade que irá possibilitar a próxima geração de experiências personalizadas, tanto para os comerciantes como para os consumidores.

Na sequência da nossa visão geral de engenharia anterior sobre o Ask DoorDash, esta terceira publicação da série do blogue analisa em pormenor o conjunto de testes de avaliação subjacente ao sistema. Seguir-se-ão análises aprofundadas sobre a plataforma e a experiência do utilizador.


Introdução

É difícil criar um agente de IA útil quando a qualidade só é visível através de relatórios dispersos e verificações manuais. Esse foi o problema com que nos deparámos com o Ask DoorDash, a nossa experiência de encomendas automatizada lançada recentemente. Na fase inicial de desenvolvimento, a avaliação baseava-se principalmente no feedback dos colaboradores e em testes manuais. Esses sinais ajudaram, mas eram escassos e tendiam a centrar-se nos cenários que já sabíamos que devíamos procurar.

Criámos um conjunto de testes de avaliação para tornar a qualidade dos agentes observável em grande escala. O indicador de qualidade passou de uma média de 1 feedback enviado por colaborador para 2 000 sessões avaliadas automaticamente por dia. Esse indicador mais abrangente ajudou-nos a detetar mais cedo falhas dos agentes que minavam a confiança e a dar prioridade aos padrões de falha recorrentes; ao agirmos em conformidade, conseguimos uma melhoria de 8 pontos nas pontuações de qualidade dos agentes antes do lançamento a nível nacional – reduzindo as taxas de erro quase para metade e atingindo o nosso objetivo de lançamento em produção. O conjunto de testes de avaliação também tornou a validação pré-lançamento muito mais rápida: um teste de regressão abrangente que anteriormente demorava mais de 6 horas a ser realizado manualmente é agora executado em cerca de 20 minutos, tornando viável a avaliação de alterações tão significativas como uma migração do modelo de base que reduziu a latência em 35%, mantendo a qualidade.

Este artigo explica como desenvolvemos esse conjunto de ferramentas de avaliação: as rubricas que definem o sucesso, o gerador de transcrições que reconstrói as sessões, o simulador que cria execuções offline repetíveis e o avaliador LLM calibrado que torna a avaliação ao nível da sessão escalável.

Os Desafios Fundamentais

O Ask DoorDash ajuda os utilizadores a descobrir restaurantes ou a fazer compras de mercearia através de conversas com várias trocas de mensagens. Por trás da conversa, o agente recorre a ferramentas para interagir com o sistema DoorDash e agir em nome do utilizador. Isso significa que a qualidade do agente tem de ser avaliada ao longo de toda a interação, e não apenas com base numa única resposta. A avaliação deve ter em conta tanto as mensagens dirigidas ao utilizador como as chamadas ocultas às ferramentas.

Figura 1: A avaliação deve ter em conta tanto a conversa visível como a trajetória oculta da ferramenta.

A criação de um conjunto de testes de avaliação para o Ask DoorDash implica transformar o problema aberto da qualidade dos agentes em requisitos concretos do sistema. A tabela abaixo resume os principais desafios que tivemos de resolver e a forma como cada um deles influenciou a conceção.

DesafioRequisito
Objetivos abertos. Um objetivo do utilizador tem vários resultados aceitáveis; raramente existe uma única resposta correta. Expresse cada objetivo sob a forma de uma rubrica – critérios suficientemente específicos para que a avaliação seja sempre feita da mesma forma, mas suficientemente abrangentes para reconhecer os diferentes caminhos válidos que uma tarefa permite.
Visibilidade da execução. A qualidade do agente não pode ser avaliada apenas com base na conversa. O avaliador também precisa de ver o que o agente fez nos bastidores. Critérios diferentes requerem diferentes partes desse registo de execução.Registe a sessão e reconstrua-a em visualizações específicas para cada critério, de modo a que cada avaliador veja a conversa e os detalhes da execução necessários para esse item da rubrica, sem ruído irrelevante no registo.
Não há ensaio seguro. Não é possível testar uma alteração em utilizadores reais, nem reproduzir uma sessão anterior com um agente modificado. Gerar sessões a pedido: um utilizador simulado conduz o agente através de um cenário escolhido, mantendo-se os dados contextuais fixos, de modo a que o mesmo cenário se desenrole sempre da mesma forma.
Avaliação em grande escala. Determinar se uma sessão ajudou o utilizador requer um julgamento ao nível humano, mas a avaliação humana não é escalável.Um juiz automatizado que substitui um avaliador humano: um juiz baseado num modelo de linguagem de grande escala (LLM), calibrado com base em sessões rotuladas por humanos, para que os veredictos sejam fiáveis.
Dois ambientes. A qualidade no desenvolvimento e a qualidade na produção podem divergir, e precisamos de ambas.Avalie sessões reais e simuladas utilizando a mesma rubrica e o mesmo avaliador, para que um resultado obtido em ambiente offline seja aplicável à produção e uma falha ocorrida em tempo real possa ser reproduzida em ambiente offline.

O que avaliamos e onde

O Ask DoorDash utiliza uma arquitetura multiagente. A mensagem de um utilizador chega primeiro ao agente Orchestrator, que encaminha o pedido para um agente de domínio especializado (por exemplo, o Agente de Descoberta de Restaurantes). O agente de domínio selecionado, por sua vez, ou trata diretamente da conversa ou devolve o controlo ao Orchestrator quando o pedido precisa de ser reencaminhado. 

O sistema de avaliação reflete essa estrutura. Uma vez que as diferentes falhas surgem em diferentes pontos do pipeline, avaliamos cada camada no ponto em que é possível medi-la de forma mais direta: o encaminhamento no Orchestrator, as medidas de segurança ao longo de todo o fluxo e a capacidade específica de cada tarefa em cada agente de domínio.

Figura 2: A avaliação reflete a arquitetura de tempo de execução do agente.

No interior do conjunto de cabos de avaliação

Por trás destas avaliações está um único conjunto de componentes cujas partes correspondem aos requisitos descritos anteriormente. Uma rubrica define o que constitui uma boa sessão, e um gerador de transcrições transforma uma sessão em bruto num documento legível para um avaliador. Um modelo de linguagem de grande escala (LLM), calibrado em relação a revisores humanos, atribui uma pontuação a essa transcrição com base na rubrica. Um simulador gera então sessões a pedido para execuções offline. Como estas partes se complementam, abordamo-las nessa ordem específica, começando pela rubrica.

Rubrica

Uma rubrica define os critérios utilizados para avaliar uma sessão. Elaborar uma boa rubrica requer um equilíbrio entre a especificidade e a generalizabilidade. Os critérios devem ser suficientemente específicos para permitir uma classificação consistente, mas suficientemente generalizáveis para aceitar todas as respostas válidas. Uma vez que muitas tarefas não têm uma única «resposta certa», a rubrica deve descrever como é uma sessão bem-sucedida, em vez de prescrever um resultado exato. A tabela abaixo apresenta exemplos de dimensões e critérios de cada avaliação. Cada critério é classificado através de uma marcação binária, e as marcações individuais são agregadas numa pontuação final ao nível da sessão.

AvaliaçãoAgenteDimensãoExemplo de critério
Barreira de segurançaTodos os agentesQualidade da comunicaçãoO agente limitou-se a dar uma resposta sucinta e não explicou o seu raciocínio ao utilizador.
Confiança e integridadeO assistente não apresentou informações comprovadamente falsas nem fez afirmações que contradissessem categoricamente o que foi mostrado ao cliente
CapacidadeDescoberta de RestaurantesSatisfação de restriçõesOs restaurantes recomendados cumprem os critérios explícitos do pedido no que diz respeito ao prazo de entrega, orçamento e restrições alimentares.
Diversidade de resultadosO conjunto oferece uma verdadeira variedade, em vez de peças quase idênticas
Compras de supermercadoExecução das comprasOs pedidos explícitos do utilizador para alterar a lista de compras são executados.
Relevância do artigoOs itens selecionados são relevantes para os objetivos do utilizador no estado.

O que se considera um bom fluxo depende da intenção do utilizador. Uma encomenda de produtos de mercearia, um pedido de receita e uma pesquisa de restaurante definem o sucesso de forma diferente. Alguns fluxos também dependem de ferramentas ou competências criadas especificamente para o efeito, pelo que nem todos os critérios se aplicam a todas as sessões.

Cada critério tem uma etapa de elegibilidade. Antes de o avaliar, o juiz decide primeiro se o critério se aplica à sessão. Se não se aplicar, o juiz ignora-o. Desta forma, o fluxo de uma receita não é penalizado por não incluir um passo específico da reordenação. As rubricas de «guardrail» e de «capacidade» funcionam desta forma, tanto em sessões de produção reais online como em sessões simuladas offline.

Fora de linha, também utilizamos uma rubrica em formato de lista de verificação: uma lista específica para cada cenário que indica o que uma execução correta deve produzir. Numa sessão intitulada «Lista de compras para tacos vegetarianos para duas pessoas por menos de 60 dólares», a lista de verificação garante que todos os itens são ingredientes relevantes para a confeção de tacos vegetarianos e que o subtotal é igual ou inferior a 60 dólares.

Estas verificações proporcionam à avaliação offline um sinal com menor variância do que os critérios amplos e de uso geral, por si só. Permitem-nos testar comportamentos específicos diretamente, tornando uma amostra pequena mais útil e favorecendo uma iteração mais rápida. Os pormenores da construção da configuração de teste são abordados na secção sobre o simulador de conversação, abaixo.

Cada critério é redigido de forma a poder ser verificado a partir da própria sessão. Isto permite que a mesma rubrica seja utilizada por um avaliador humano durante a calibração e pelo juiz do LLM em grande escala. Ambos necessitam da sessão num formato legível, que é precisamente o que o gerador de transcrições produz.

Gerador de transcrições

Equipamos cada sessão do agente com o OpenTelemetry. Cada sessão transforma-se num traço, que armazenamos numa instância interna do ClickHouse. Os seus «spans» registam os passos que o agente realizou, incluindo a entrada do utilizador, a saída do modelo, as chamadas e respostas das ferramentas e os widgets apresentados ao utilizador. Esse registo completo é a matéria-prima a partir da qual cada avaliação tem início.

O registo bruto está completo, mas é difícil de avaliar diretamente. Algumas respostas das ferramentas são muito extensas e grande parte do conteúdo mantém o registo bem estruturado, como a estrutura do esquema e os campos que não fornecem qualquer indicação sobre a qualidade do agente. As evidências relativas a um critério também podem estar espalhadas por vários intervalos ou turnos, pelo que têm de ser reorganizadas antes de um avaliador as poder utilizar.

O gerador de transcrições é um conjunto de scripts em Python que processa os registos armazenados antes da avaliação. Reúne os segmentos dispersos, remove os tokens que não contêm qualquer indicação de qualidade e reduz as cargas excessivamente grandes. O resultado é uma visão compacta da conversa e do trabalho que está por trás dela. Um avaliador pode classificar essa visão de forma mais consistente do que o registo em bruto.

Nem todos os critérios necessitam de uma visão global. Cada critério declara as provas de que depende, e o criador apresenta ao juiz apenas essa parte específica. Uma verificação de fundamentação obtém a afirmação do agente e o resultado da ferramenta subjacente a essa afirmação. Uma verificação de diversidade analisa as recomendações e o pedido original. Uma verificação de narração analisa o texto que o agente transmitiu enquanto trabalhava. Delimitar o âmbito das provas mantém cada julgamento focado.

Figura 3: O gerador de transcrições transforma registos brutos em visualizações específicas para cada critério, permitindo uma avaliação mais focada e consistente.

Simulador de conversação

O Simulador de Conversação permite-nos avaliar um agente candidato antes de este entrar em contacto com utilizadores reais, gerando sessões realistas com um utilizador simulado – um LLM a desempenhar o papel do cliente. Cada execução parte de um cenário que define o pedido inicial, o objetivo do utilizador e a forma como este deve reagir a perguntas ou resultados, produzindo conversas comparáveis com várias trocas de mensagens. Quando um cenário depende de um estado externo, como encomendas anteriores na Safeway, um carrinho em curso ou o inventário da loja, o sistema utiliza «fixtures»: cargas úteis gravadas que são devolvidas em vez de chamadas em tempo real. Isto mantém cada execução fixada no mesmo estado, evitando desvios decorrentes de alterações no catálogo, disponibilidade na loja ou histórico da conta de teste. Por exemplo, o cenário de reabastecimento começa com «Reabastecer os meus produtos habituais» e fixa a função `get_reorder_items` a um histórico de encomendas gravado.

// simulating reorder scenario
{
  "evalId": "mt-reorder",
  "sessionInput": {
    "state": {
      "__tool_fixture_pack_names": [
        "reorder_history_v1"
      ],
      "max_turns": 3
    }
  },
  "conversationScenario": {
    "startingPrompt": "Reorder my usuals",
    "conversationPlan": "You want to reorder your usual groceries.."
  }
// ...
}// reorder_history_v1 — returned in place of the live get_reorder_items call
{
  "success": true,
  "orders": [
    { "store_name": "Albertsons", "order_date": "2026-04-22T18:30:00Z",
      "items": [
        {"name": "Meadow Gold Whole Milk Jug (1 gal)",     "quantity": 2},
        {"name": "Oroweat 100% Whole Wheat Bread (24 oz)", "quantity": 1},
        {"name": "Signature Select Hass Avocados (5 ct)",  "quantity": 1},
        {"name": "Ben & Jerry's Half Baked Ice Cream",     "quantity": 1}
        // ... more items
      ] }
    // ... more historical orders
  ]
}

Em cada execução deste cenário, surgem exatamente estas encomendas. Isso torna o resultado esperado previsível: a lista selecionada pelo agente deve ser extraída do histórico fixo de encomendas, e o avaliador pode classificá-la com base nesse mesmo conjunto de itens em todas as ocasiões.

A simulação de conversas tornou viável a parte de introdução de dados na avaliação offline. No caso do Ask DoorDash, um conjunto abrangente típico abrange 50 cenários com 8 tentativas cada, o que resulta em 400 conversas geradas. Sem a simulação, gerar esse conjunto significaria que um programador teria de conversar com o agente localmente, uma conversa de cada vez. A cerca de 1 minuto por conversa, isso demoraria mais de 6 horas. O simulador reduz essa etapa de geração para cerca de 20 minutos.

Mestrado em Direito (LLM) como juiz

Na prática, a avaliação de agentes é um problema de medição com um ciclo de feedback rigoroso. Precisamos de amostras suficientes para estimar a qualidade da produção diariamente e de resultados rápidos o suficiente para detetar regressões antes que estas se propaguem. A mesma restrição aplica-se antes do lançamento: cada alteração candidata gera as suas próprias sessões, e a avaliação tem de ser devolvida com rapidez suficiente para se manter no ciclo de desenvolvimento, em vez de se tornar um estrangulamento no lançamento.

Essa escala exclui a revisão humana como o processo de avaliação por defeito. Um revisor pode tomar a decisão certa numa transcrição complexa, mas a análise de cada sessão é dispendiosa. Pode incluir muitas interações, chamadas a ferramentas, resultados de modelos e widgets renderizados, e o revisor tem de relacionar as afirmações do agente com as provas presentes no registo.

A revisão humana continua a ser essencial, mas recorremos a ela nos aspetos em que tem maior impacto: na definição de critérios de avaliação, na rotulagem de conjuntos de calibração e na auditoria do comportamento dos avaliadores. A maior parte da avaliação é realizada por um avaliador LLM, que lê a transcrição preparada e atribui uma pontuação com base nos critérios de avaliação.

Um juiz de LLM só é útil se chegar às mesmas conclusões que os revisores humanos.

Aplicámos a otimização do prompt GEPA para aperfeiçoar os limites de decisão do avaliador. O algoritmo propõe, de forma iterativa, revisões ao prompt do avaliador e mantém aquelas que melhoram a concordância com os rótulos humanos num conjunto de validação. Esta calibração é contínua: à medida que as rubricas evoluem, por exemplo, quando são adicionadas novas capacidades, recolhemos novos rótulos e recalibramos o avaliador.

O juiz avalia cada critério de forma independente. Recebe o critério e as provas em que este se baseia e, em seguida, emite um veredicto acompanhado de uma breve justificação. Com esta configuração, expandimos a monitorização da qualidade de cerca de um feedback enviado por um colaborador para 2 000 sessões avaliadas automaticamente por dia.

Serviço de Avaliação

A criação de um conjunto de testes escalável depende em grande medida de uma infraestrutura de plataforma robusta. O armazenamento de traços, a execução de testes em tempo real, o desenvolvimento de critérios de avaliação baseados na interface do utilizador e os fluxos de trabalho de anotação têm um custo. Começámos com uma equipa de plataforma incorporada que desenvolveu estes componentes em paralelo com o conjunto de testes do Ask DoorDash, criando um ciclo de feedback estreito entre o desenvolvimento da plataforma e a conceção dos testes. Isso permitiu que a infraestrutura e o conjunto de testes evoluíssem em conjunto. Essa colaboração estreita acelerou o desenvolvimento dos testes para o Ask DoorDash e está agora a dar forma a um serviço de testes partilhado — um caminho já traçado que outras equipas da DoorDash podem adotar rapidamente, ao mesmo tempo que o personalizam para os seus próprios casos de utilização.

O Circuito de Retroalimentação

O conjunto de testes de avaliação proporciona-nos uma forma escalável de medir a qualidade do agente. A próxima questão é saber como é que essa medição altera o ciclo de desenvolvimento.

Agrupamento de falhas por temas

Uma execução diária de avaliação pode revelar muitas falhas, mas analisá-las uma a uma torna o ciclo de iteração orientado por casos pontuais. A falha mais recente ou mais surpreendente pode dominar a correção, mesmo quando não é o modo de falha mais comum. Precisamos de identificar os padrões mais amplos por trás das falhas e a frequência com que ocorrem, para que possamos dar prioridade aos problemas com maior impacto ao longo das sessões.

A rubrica constitui um ponto de partida útil. Cada sessão com falhas já inclui o critério que violou, pelo que os critérios da rubrica funcionam como grupos naturais de problemas. Uma falha de fundamentação, a ausência de uma substituição e uma falha de narração deficiente são problemas diferentes e devem, normalmente, ser analisados separadamente. Agrupar as falhas desta forma transforma uma longa lista de exemplos num conjunto de temas ordenados por importância.

Da deteção à resolução

A análise de agrupamentos indica-nos o que falha com maior frequência, mas determinar a causa da falha e a forma de a resolver requer o contexto de implementação. À medida que o desenvolvimento impulsionado pela IA torna mais rápida a produção de código e de alterações, o gargalo passa a ser a escolha da alteração certa e a validação do seu impacto. Partindo de um agrupamento de falhas, um agente de codificação pode inspecionar os registos de falhas, os percursos de código relevantes, as alterações recentes e as investigações anteriores. Quando a correção estiver clara, pode elaborar diretamente um pedido de integração; no caso de alterações imediatas ou de aprendizagem no contexto, produz um diagnóstico e uma proposta de alteração para revisão humana.

Competências do agente

Organizamos estes fluxos de trabalho de desenvolvimento orientados para a avaliação sob a forma de «Agent Skills». Cada «skill» define uma tarefa repetível, como a classificação de falhas em clusters ou a investigação de um modo de falha. Isto facilita a adoção do fluxo de trabalho por outras equipas e permite-nos melhorá-lo mais facilmente ao longo do tempo. Quando uma «skill» classifica incorretamente um cluster ou propõe uma correção ineficaz, atualizamos a «skill» em vez de corrigirmos uma execução pontual, e essa melhoria é aplicada às futuras invocações.

O conjunto de ferramentas de avaliação fornece o sinal, e as Competências do Agente transformam esse sinal num fluxo de trabalho operacional. Em conjunto, tornam o conjunto de ferramentas mais do que um simples painel de resultados: um plano de controlo para iteração que integra a monitorização da produção, a agrupamento de falhas, a investigação assistida por agentes e a validação pré-lançamento num único ciclo.

Figura 4: O ciclo de retroalimentação transforma a avaliação numa trajetória contínua que vai das falhas observadas às melhorias validadas.

Do sinal de avaliação ao impacto na produção

Reduzir a fuga de raciocínio

Um caso recente envolvendo um agente de compras de supermercado ilustra na prática o ciclo de desenvolvimento orientado pela avaliação. A avaliação diária online revelou um pico de «fuga de raciocínio». O agente concluiu a tarefa, mas a sua narração dirigida ao utilizador utilizava, ocasionalmente, linguagem orientada para o sistema, como «reorder skill», nomes de ferramentas ou expressões próprias de software, como «fetch» e «in parallel». A resposta parecia mais a de um agente de programação do que a de um assistente de compras.

O diagnóstico apontou para uma intervenção imediata, pelo que consolidámos as regras de comunicação do agente e separámos as instruções internas relativas às competências da linguagem dirigida ao utilizador. Validámos a alteração offline com os cenários mais propensos a este modo de falha no tráfego de produção e observámos uma redução da taxa de fuga de 11%. Também realizámos uma avaliação exaustiva dos cenários principais e não detetámos quaisquer regressões atribuíveis. Após a implementação, a monitorização online confirmou uma melhoria significativa.

Figura 5: O Eval permitiu identificar e reduzir a fuga de raciocínio nas mensagens dirigidas aos utilizadores.

Reduzir os riscos associados à migração para um modelo base

Quando o Gemini 3.5 Flash foi lançado em meados de maio, os nossos agentes estavam a funcionar com o Claude Sonnet 4.6. Os resultados dos testes de desempenho do Flash apontaram para uma oportunidade: reduzir a latência do Ask DoorDash para que o agente parecesse mais ágil e não travado. No entanto, a substituição do modelo base é arriscada, pois pode alterar o comportamento dos agentes em todo o sistema. Precisávamos de ter a certeza de que a qualidade se manteria antes de expor os utilizadores a essa mudança.

Submetemos o Flash ao conjunto de testes offline e as pontuações caíram drasticamente. A avaliação revelou padrões concretos de falha e direcionámos os agentes de codificação de IA para esses padrões, a fim de formular hipóteses e realizar experiências. O que se descobriu não foi uma lacuna de capacidade, mas sim de compatibilidade. O Flash formatava alguns parâmetros da ferramenta de forma diferente do Sonnet e interpretava partes de um prompt do sistema que tinham sido ajustadas implicitamente de acordo com as convenções do Sonnet. As baixas pontuações refletiam um sistema adaptado ao Sonnet, e não um modelo mais fraco.

That diagnosis pointed to a set of small, targeted fixes. Some were deterministic guards on tool inputs – Flash would occasionally pass a search query as a JSON object like {“dishes”: […]} where the tool’s signature declared a plain string, so we coerced these back into the expected shape instead of dropping the call. Others were prompt updates that stated explicitly what Sonnet had inferred on its own, such as using the exact store name from the data and not embellishing beyond what the tool results support. We were not changing the model; we were correcting the environment around it.

Ao repetir a avaliação, o Flash voltou a atingir a mesma qualidade do Sonnet, dentro da margem de variação do sistema de testes. Migrámos os agentes de produção e monitorizámos a qualidade e o envolvimento no tráfego em produção. O resultado positivo manteve-se: uma redução de 35% na latência, sem qualquer perda nas métricas de qualidade.

Juntamente com o exemplo da fuga de raciocínio, isto demonstra que o sistema de testes funciona nos dois sentidos, transformando sinais de qualidade da produção em correções e reduzindo os riscos associados a alterações deliberadas no sistema antes da sua implementação.

Lições aprendidas

Os juízes precisam de visualizações das transcrições adaptadas à questão. O registo bruto contém frequentemente mais informação do que o critério exige. Alguns segmentos não estão relacionados com o critério. Mesmo os segmentos úteis podem incluir estruturas de esquema, campos repetidos e tokens de carga útil que existem apenas para manter os dados bem formados. Obtemos melhores resultados removendo o que é irrelevante e mantendo as evidências de que o critério depende efetivamente.

O ambiente tem de ser controlado, caso contrário a pontuação mede o ruído. Tanto a variação a montante como o próprio não determinismo do agente podem alterar um resultado, o que torna difícil determinar se uma alteração contribuiu efetivamente para a melhoria. Os «fixtures» congelam o mundo a montante e os cenários fixos mantêm as condições da tarefa estáveis, pelo que é mais provável que a variação das métricas reflita o agente em si, em vez do seu ambiente.

Um resultado offline só tem importância se se refletir no online. É por isso que se utilizam a mesma rubrica e o mesmo avaliador calibrado em ambos os contextos. Se o online e o offline utilizarem avaliadores ajustados separadamente, as suas pontuações podem divergir de formas difíceis de conciliar.

O sistema de avaliação também gera os seus próprios relatórios de erros. Uma parte significativa das falhas assinaladas deve-se a erros no conjunto de testes, e não a problemas com os agentes; trata-se, normalmente, de um falso positivo do avaliador ou de uma lacuna no rastreio que proporciona ao avaliador uma visão incompleta. Resolver esses problemas em paralelo mantém a fiabilidade do sistema de avaliação.

Conclusão

Para os agentes de produção, a avaliação não pode ser uma questão secundária. Tem de fazer parte do sistema desde o início. Mas a avaliação das sessões é apenas o primeiro passo. O desafio maior consiste em transformar essas pontuações num melhor comportamento dos agentes.

O sistema preenche a lacuna entre a medição e a melhoria. Alargou a monitorização diária da qualidade, passando de cerca de 1 feedback enviado por um colaborador para 2 000 sessões avaliadas automaticamente, e reduziu os testes de regressão abrangentes de mais de 6 horas, realizados manualmente, para cerca de 20 minutos. Esses ganhos permitiram-nos detetar problemas de produção mais cedo, dar prioridade a modos de falha recorrentes e validar grandes alterações antes de estas chegarem aos utilizadores. O resultado foi um impacto mensurável na produção, incluindo uma melhoria de 8 pontos nas pontuações de qualidade dos agentes antes do lançamento a nível nacional e uma migração validada do modelo base que reduziu a latência em 35%, mantendo a qualidade.

Um conjunto de testes escalável requer uma infraestrutura de plataforma robusta. A estreita colaboração com a equipa da plataforma acelerou o desenvolvimento do conjunto de testes do agente para o Ask DoorDash. Este trabalho de base está agora a dar forma a um serviço de avaliação partilhado.

Eis que surge a advertência obrigatória «A IA avança rapidamente»: quando redigi este artigo pela primeira vez, no início de março, comecei por referir que «a IA está a chegar» e que devíamos preparar-nos para ela. Ao reler isso apenas alguns meses depois, a frase parece-me notavelmente desatualizada: a IA já não está apenas a chegar; já chegou. As grandes empresas de tecnologia estão agora a gerar mais de 75% do seu código através da IA, e as empresas mais pequenas e menos avançadas estão rapidamente a seguir o exemplo. Quando ler este artigo, esta premissa poderá já parecer tão ridícula como a minha anterior!

No entanto, acredito que as lições a retirar deste artigo continuam a ser relevantes. O que abordo aqui é a minha trajetória pessoal rumo à proficiência em IA. Tendo tudo isto em conta, considero-me, na melhor das hipóteses, um utilizador de nível intermédio, mas chegar até aqui exigiu muita experimentação e muito mais audição de podcasts do que estou disposto a admitir. A parte que ainda parece não estar resolvida para muitos programadores não é utilizar a IA para o preenchimento automático ou edições rápidas, mas sim confiar a um agente uma tarefa mais complexa e obter em troca algo coerente, passível de revisão e útil. Se esse fluxo de trabalho ainda não te fez sentido, este artigo é para ti.

Nas próximas secções, vou reconstituir o meu percurso, desde o momento em que o Cursor era o meu companheiro de programação de IA por predefinição, passando pelas minhas primeiras tentativas confusas de utilizar o Claude Code, até às experiências com equipas de agentes que, na sua maioria, resultaram em código de IA de má qualidade e dispendioso, e finalmente ao ciclo de investigação → planeamento → implementação que tornou prático o trabalho com agentes de execução prolongada.

Em seguida, vou apresentar um pequeno projeto a que chamo «Agentic Orchestrator», que resume todas estas lições. Conforme se pode ver na Figura 1, trata-se de um orquestrador simples, baseado numa máquina de estados, que criei para transformar os agentes em projetos ambiciosos de execução única. E a melhor parte? É de código aberto

Figura 1: O painel principal do Agentic Orchestrator, que acompanha várias funcionalidades em diferentes fases de desenvolvimento.

Mas vamos começar pelo início.

Antes do ciclo: a minha fase do cursor

O Cursor é uma ferramenta incrível para aumentar a produtividade, que combina a conveniência de um ambiente de desenvolvimento totalmente integrado com o poder da IA agente para lidar com tarefas de vários níveis de complexidade. Tem vindo a melhorar ao longo do tempo, à medida que os modelos e as ferramentas se tornam mais capazes. Tornou-se a minha ferramenta de eleição em 2025 e até 2026. Não me lembro de ter escrito muitas linhas de código manualmente, a menos que contar o uso incessante da tecla Tab.

Mas, à medida que a minha curiosidade sobre até onde poderia levar o desenvolvimento de agentes aumentava, comecei a deparar-me com as limitações do Cursor e do desenvolvimento orientado por separadores em geral. O principal problema era que não conseguia que o sistema funcionasse de forma fiável durante tempo suficiente para criar funcionalidades complexas com total autonomia. 

Com as funcionalidades revolucionárias introduzidas pelo Opus da Anthropic, o setor já falava de agentes de execução prolongada e da sua capacidade de identificar características de forma imediata, sem intervenção humana. Eu queria experimentar tudo isso por mim próprio! Por isso, estava na hora de dar o salto para a nova sensação de que toda a gente falava: o Claude Code.

A minha relação complexa com Claude Code

Imagina o seguinte: os teus amigos e colegas estão a gabar-se de como conseguem automatizar todos os aspetos das suas vidas, enquanto as tuas tentativas de automatizar até as coisas mais simples falham redondamente. Era assim que eu me sentia em março de 2026.

Várias pessoas de diferentes empresas e com diferentes formações técnicas tentavam convencer-me de que já tínhamos atingido a singularidade. Já não eram necessários humanos no processo, diziam eles; a IA iria concretizar todos os teus desejos e torná-los realidade, incluindo fazer-se passar por ti no Slack, atender chamadas no Zoom, preparar um café expresso fantástico e, claro, escrever código. Tudo o que eu precisava de fazer, diziam eles, era mudar para o Claude Code com o Opus 4.6 para ver o meu mundo virado do avesso. E foi o que fiz! Para mim, porém, acabou por ser pior do que usar modelos equivalentes com o Cursor.

Gosto tanto de uma boa ferramenta baseada em terminal como qualquer outro programador, mas com o Claude Code perdi a capacidade de interagir diretamente para rever cada alteração individual. Em teoria, isto seria compensado por uma melhor orquestração e pelas ferramentas fornecidas pelo harness, mas, no final de contas, o modelo continuava a ser o mesmo. E não é como se o Cursor não tivesse ferramentas ou um modo de planeamento. Então, o que se passa?

Afinal, o Claude Code implica uma mudança de mentalidade: já não é necessário interagir com o código. É preciso alterar o fluxo de desenvolvimento, confiar no agente e iterar totalmente através de prompts. 

Estava na hora de começar a experimentar e tentar conseguir aquele espresso de sonho, preparado com a máquina Opus.

Início das experiências: Agentes, agentes, agentes!

Lembra-te do meu objetivo principal: fazer com que a IA desenvolva uma característica complexa de forma autónoma.

Ao explorar a documentação do Claude Code, descobri uma funcionalidade experimental chamada «equipas de agentes». Esta permite aos utilizadores invocar uma equipa de agentes com diferentes personalidades e funções para «hackear» diferentes partes da base de código e, em seguida, fundir os seus resultados num produto final funcional — ou, pelo menos, essa é a teoria.

Na prática, a minha experiência com este enxame de agentes correu mal por razões que não esperava. Inicialmente, presumi que a parte mais difícil seria a formulação das instruções, por isso concentrei-me nisso. Criei personagens elaboradas, disse a um agente que era «o melhor programador de Go que já existiu», designei outro como um «revisor sénior meticuloso» e planeei como iriam passar o trabalho uns aos outros. Eu era, essencialmente, um influenciador motivacional para modelos de linguagem. Mas nada disso fez a diferença. A lisonja não acrescenta capacidade! O modelo não guarda as suas verdadeiras competências na reserva até que lhe digas que é brilhante. A personagem era teatro; por baixo desse teatro, o resultado era exatamente tão bom ou tão mau quanto sempre iria ser.

O meu fiasco com as personas foi, na maior parte, inofensivo. Na pior das hipóteses, foi um desperdício de contexto. O verdadeiro problema foi que o paralelismo multiplica a divergência. Cada agente trabalhou com base na sua própria interpretação de um documento de especificações ambíguo, fez as suas próprias suposições independentes e inventou a sua própria versão da interface onde duas partes deveriam encaixar-se. Cada agente era localmente plausível. Nada era globalmente consistente. Assim, quando chegou a altura de fazer a fusão, as juntas não se alinhavam; havia contratos incompatíveis, lógica duplicada e duas metades de uma funcionalidade em que cada uma assumia que a outra metade funcionava de forma diferente.

O resultado final? No que diz respeito a funcionalidades triviais, as equipas de agentes funcionaram adequadamente. No entanto, para qualquer coisa que pudesse realmente justificar a complexidade de uma configuração multiagente, o código de IA resultante dessas sessões era completamente inutilizável. Acabei por rejeitar pull requests inteiros que tinham levado horas a processar, precisamente por esta razão.

Por isso, dei um passo atrás. A lição não era que os agentes não conseguem lidar com a complexidade, nem que ter mais agentes é pior do que ter apenas um. Era que eu tinha estado a fazer a pergunta errada. Continuava a tentar fazer com que os agentes colaborassem, se coordenassem, chegassem a acordo e unissem o seu trabalho, quando o verdadeiro problema era que não havia nada em torno do qual se pudessem coordenar. Não havia uma fonte de verdade partilhada. Não havia artefactos a serem passados de uma etapa para a seguinte. Não havia separação entre pensar e agir. Os agentes não eram o problema. O problema era o espaço vazio entre eles. Por isso, deixei de perguntar como fazer com que uma equipa de agentes trabalhasse em conjunto e comecei a perguntar o contrário: que estrutura permitiria libertar cada agente da necessidade de colaborar, para que pudesse realizar apenas uma tarefa específica e bem definida, com base num artefacto fixo que já tivesse sido produzido? Deixou de se tratar de «atirar agentes para o problema» e passou a ser mais uma questão de construir os carris e deixar os agentes percorrê-los um troço de cada vez. 

Foi esta nova abordagem que acabou por me levar por um caminho muito diferente.

O meu ponto de viragem: Investigação, entrevistas, planeamento, iteração e revisores críticos

Depois do fiasco com a equipa de agentes, fiz algo radical: falei com pessoas — ou seja, seres humanos de verdade. Procurei engenheiros que, de alguma forma, tivessem descoberto o segredo para fazer com que o Claude realizasse um trabalho significativo de forma autónoma. O que aprendi mudou completamente o meu modelo mental.

A primeira revelação surgiu de uma palestra inspiradora da Humanlayer sobre o que chamam de estrutura RPI — investigação, planeamento, implementação. A ideia é aparentemente simples: em vez de atirar um conjunto completo de especificações a um agente e esperar pelo melhor, divide-se o trabalho em fases cognitivas distintas. Primeiro, o agente pesquisa a base de código, lendo os ficheiros relevantes, compreendendo os padrões e mapeando as dependências. Em seguida, elabora um plano — um documento de implementação detalhado e faseado, com percursos de ficheiros específicos, trechos de código e critérios de sucesso. Só depois de concluído é que se procede à implementação, trabalhando a partir do seu próprio plano, em vez de uma compreensão vaga de uma especificação que já se tinha esquecido parcialmente há 30 000 tokens.

Esta era uma filosofia fundamentalmente diferente da abordagem «aqui está a especificação, vai construí-la» que eu vinha a utilizar. Cada fase tem um objetivo restrito e bem definido. Cada fase produz um artefacto concreto que se torna a entrada para a fase seguinte. E, fundamentalmente, cada fase encaixa-se confortavelmente numa janela de contexto, porque não tenta abranger todo o problema de uma só vez. A investigação não precisa de pensar nos detalhes de implementação. O planeamento não precisa de escrever código. A implementação não precisa de redescobrir a base de código, porque o plano já lhe diz exatamente onde procurar.

A segunda revelação foi ainda mais simples, cortesia da competência «grill-me» de Matt Pocock: deixa que o agente te entreviste. Em vez de passares horas a escrever o prompt perfeito que antecipe todos os casos extremos e esclareça todas as ambiguidades, deixa que o agente te faça perguntas! Afinal, uma sessão de perguntas e respostas com um agente que acabou de pesquisar a tua base de código vale mais do que horas de engenharia de prompts antecipada. O agente sabe do que precisa; só tens de lhe dar permissão para perguntar. Ficarias surpreendido com o quão relevantes estas perguntas podem, por vezes, ser. No fim de contas, faz todo o sentido; uma das coisas que se aprende à medida que se avança na carreira é delegar. Quando o fazes, na maioria das vezes os engenheiros a quem delegaste trabalho voltam com perguntas. Eles não querem fazer suposições incorretas sobre o que tinhas em mente. Porque é que os agentes seriam diferentes?

Mas a terceira e mais importante lição que aprendi foi sobre o que a indústria costuma chamar de «loop».

É que, mesmo que implemente o RPI, ainda tem dois problemas. O primeiro é o problema da janela de contexto: uma implementação complexa pode exigir mais tokens do que uma única sessão consegue conter. O agente pode chegar a 80% do plano e, de repente, começar a perder a coerência. Ou pode deparar-se com um erro de compilação inesperado que o faça entrar numa espiral. O segundo problema é mais subtil: a autoavaliação é praticamente inútil. Um agente que acabou de passar meia hora a implementar uma funcionalidade não deveria ter de decidir, em seguida, se essa implementação é realmente boa.

O «loop» resolve o primeiro problema. Um revisor antagonista resolve o segundo. E a parte mais bonita? Tudo isto é embaraçosamente simples. Na sua essência, continua a ser basicamente um loop «while» do Bash, apenas com um «gate» de revisão, como se mostra aqui:

while [ iteration < max_iterations ]; do
      run the implementation agent with the objective and current progress
      check if the agent reported SUCCESS or RETRY
      if RETRY → feed the progress back in and go again
      if SUCCESS → run an antagonistic reviewer in a fresh context window
          if APPROVED → done
          if CHANGES_REQUESTED → feed the review back in and go again
  done

É isso mesmo. É esse o segredo. Executa-se o agente, este realiza algumas tarefas, atualiza um ficheiro de progresso indicando «eis o que já fiz, eis o que ainda falta» e, em seguida, emite um sinal: AGENT_LOOP_STATUS: SUCCESS ou AGENT_LOOP_STATUS: RETRY. Se indicar RETRY, inicias uma nova sessão com uma janela de contexto limpa, mas passas-lhe o ficheiro de progresso para que saiba onde ficou. Se indicar SUCCESS, não confias nele. Convocas o antagonista.

Por «revisor antagonista», não me refiro a um agente que seja rude ou que contrarie só por diversão (embora seja realmente divertido de ver; devias experimentar!). Refiro-me a um agente distinto, cuja função é ser profissionalmente cético. Ele lê o plano, analisa as alterações, verifica os testes, procura requisitos que tenham escapado, aponta pressupostos frágeis e faz a pergunta que o agente responsável pela implementação tem menos incentivos para fazer: «Isto está realmente concluído?»

A janela de contexto separada é a chave. Se o próprio agente rever o seu próprio trabalho, este traz consigo todos os pressupostos, atalhos e dinamismo narrativo da implementação. Lembra-se do motivo pelo qual tomou uma decisão, pelo que é mais provável que a defenda. Um revisor imparcial não tem esse apego. Este vê o estado do repositório, o objetivo definido e os artefactos de implementação. Essa separação cria pressão adversária suficiente para detetar o tipo de erros que, de outra forma, sobreviveriam até à revisão humana.

Isto resolve ambos os modos de falha da forma mais pragmática possível. Em vez de tentar fazer com que uma única sessão perdure durante toda a implementação, aceita-se que as sessões são efémeras e projeta-se tendo isso em conta. Cada iteração recebe uma janela de contexto limpa. Cada iteração lê o ficheiro de progresso, compreende o que foi feito e concentra-se no que se segue. O ficheiro de progresso torna-se a memória de longo prazo do agente, enquanto o revisor se torna o sistema imunitário que impede que um trabalho mal feito se declare silenciosamente concluído.

Os mais experientes em matéria de agentes entre vós talvez já tenham percebido que se trata de um ciclo do Ralph. O crítico antagonista acrescentou a peça que faltava; não é «continuar até estar concluído», mas sim «continuar até que um par de olhos novo concorde que “concluído” significa mesmo “concluído”».

A combinação das fases do RPI, do ciclo e da revisão antagónica representou uma verdadeira mudança radical. Pela primeira vez, pude indicar ao Claude uma funcionalidade moderadamente complexa — algo que exigiria alguns milhares de linhas de código distribuídas por vários ficheiros — e voltar a encontrar uma implementação funcional. Não funcionava sempre, mas funcionava com frequência suficiente para que eu deixasse de considerar a programação autónoma como um truque de festa e começasse a vê-la como uma ferramenta.

A ideia central era sólida: dividir o trabalho em fases, fazer com que cada fase produzisse artefactos, ultrapassar as fronteiras do contexto e nunca permitir que o agente que escreveu o código fosse o único a julgar se o código é bom.

Espaço de trabalho desarrumado

Embora estivesse entusiasmado com a minha capacidade de criar elementos complexos com muito poucos ajustes finais após este avanço, deixei-me levar pelo entusiasmo. O meu espaço de trabalho começou a ficar parecido com o da Figura 2, mas multiplicado por cinco ou seis — um para cada elemento em que estava a trabalhar em simultâneo.

Figura 2: O meu espaço de trabalho tradicional: várias sessões do Claude e painéis de terminal, cada um a acompanhar uma fase diferente do ciclo RPI.

O fluxo de trabalho funcionava, mas era complicado executá-lo manualmente. Para cada funcionalidade, tinha uma sessão de IA a pesquisar a base de código, outra a transformar essa pesquisa num plano de implementação e, depois, um ciclo em que respondia a perguntas de esclarecimento, revia o plano e dava início à implementação. Isso era viável para uma única funcionalidade. Quando tentei aplicá-lo a cinco ou seis funcionalidades ao mesmo tempo, estava constantemente a alternar entre terminais, planos, ramos e conversas a meio.

O meu primeiro instinto foi voltar a recorrer aos agentes. Encarreguei um agente gestor de implementar a estrutura RPI-plus-loop utilizando subagentes, para que eu pudesse gerir tudo a partir de uma única instância do Claude. No entanto, ainda traumatizado pela minha experiência anterior com equipas de agentes, decidi, em vez disso, criar uma ferramenta. Tenho o prazer de anunciar que esta ferramenta está agora disponível em código aberto para que todos possam usufruir dela.

Conheça o Agentic Orchestrator

E foi assim que nasceu o Agentic Orchestrator. Trata-se de uma interface de utilizador de terminal (TUI) que adota o modelo «investigação → planeamento → implementação», o aperfeiçoa e o integra numa verdadeira abordagem de engenharia.

A ideia central é simples: não peça a uma IA para gerir outras IAs. Em vez disso, crie uma estrutura que conduza o ciclo de vida das funcionalidades e deixe que cada sessão de IA se concentre em fazer bem apenas uma coisa. O orquestrador trata da infraestrutura, incluindo transições de estado, isolamento da árvore de trabalho, gestão de sessões, acompanhamento do progresso e recuperação após falhas. O agente trata do raciocínio. Trata-se de uma orquestração previsível sobre agentes imprevisíveis.

Dos loops do Bash a uma verdadeira máquina de estados

Lembras-te do ciclo «while» do Bash de que falámos anteriormente? É aquele em que se executa o agente, verifica-se o estado e se reporta o progresso. O Agentic Orchestrator parte dessa mesma ideia e concretiza-a. Cada funcionalidade passa por uma máquina de estados bem definida, conforme ilustrado na Figura 3: Base de conhecimento → consulta → pesquisa → conceção → planeamento → implementação → revisão → publicação → concluído.

Figura 3: O novo e melhorado ciclo de desenvolvimento.

O RPI tem agora um aspeto um pouco diferente do que tinha antes. Este é o resultado de muitas semanas de experimentação, que conduziram a um fluxo de trabalho que me permite produzir funcionalidades muito complexas com confiança, numa única sessão de longa duração. Não, não tenho uma sigla engenhosa para isso, mas a Tabela 1 mostra em que consiste cada passo e qual é a sua função.

FaseSaídaPor que é que existe?
Base de conhecimentoUm mapa reutilizável de arquitetura, convenções, APIs, dependências e comandos de verificaçãoPermite que cada fase subsequente comece a partir de uma base de referência real, em vez de ter de voltar a explorar o repositório do zero.
ConsultaEsclarecimento de perguntas e respostas dos utilizadores através do padrão «grill-me»Obriga a revelar as incógnitas antes que qualquer agente comece a fazer suposições.
InvestigaçãoPesquisa documental com referências concretas a ficheiros e comportamento no estado atualDescreve a realidade, e não a solução, na qual se baseará o planeamento do substrato factual. 
ConceçãoDocumento de conceção: Enquadramento do problema, solução, histórias de utilizador, decisões, itens explicitamente excluídos do âmbitoConstitui a fonte de referência da funcionalidade, resultante de uma revisão de conceção específica realizada em conjunto com o agente.
RoteiroLista ordenada de secções verticais finas, de ponta a pontaPermite que os agentes executem partes delimitadas e verificáveis, em vez de planos extensos que abrangem todas as camadas de uma só vez.
Planeamento por fasesPlano de fases aprovado, com tarefas, critérios de aceitação e expectativas de verificaçãoTorna o trabalho suficientemente concreto para ser executado; as críticas opcionais na fase de planeamento permitem detetar planos fracos antes que a implementação se torne dispendiosa.
ImplementaçãoCódigo, testes, ficheiro de progresso, relatório de verificaçãoPermite que uma nova janela de contexto retome o ponto em que a sessão anterior terminou; este ficheiro de progresso garante que o ciclo seja cumprido com rigor.
Revisão finalAprovação ou pedidos de alteração detalhadosPermite que um revisor, sem qualquer envolvimento na implementação, decida se «concluído» significa realmente «concluído».

Tabela 1: Explicação de cada etapa do novo ciclo de desenvolvimento.

A infraestrutura monótona

 As fases são a parte visível. Por baixo, o orquestrador encarrega-se do trabalho menos vistoso que transforma um fluxo de trabalho que, de outra forma, seria manual, num sistema que continua a funcionar mesmo depois de eu fechar o portátil. Nos bastidores, vários componentes mantêm tudo a funcionar:

  • Árvores de trabalho: Cada funcionalidade é executada na sua própria árvore de trabalho do Git, o que significa que várias funcionalidades podem percorrer o pipeline utilizando o mesmo repositório sem interferirem umas com as outras.
  • O TUI: Esta interface disponibiliza um painel de controlo que acompanha tudo o que está em curso — o que está em fase de pesquisa, o que está bloqueado, o que precisa de revisão e o que está pronto para publicação.
  • Recuperação após falhas e gestão de sessões: uma vez que a máquina de estados é guardada, não perderá o progresso devido a situações como o encerramento do terminal, uma sessão bloqueada ou uma falha grave.
  • Ações pós-publicação: Mesmo depois de o PR estar publicado, o orquestrador continua a dar-lhe apoio. Pode fazer um rebase em relação ao branch «main», solicitar refatorações específicas, reverter para um estado anterior, ajustar a implementação ou responder aos comentários de revisão recebidos sem sair da TUI.
  • Transições de fase determinísticas: O código decide o que se executa a seguir, mantém os artefactos e impõe os controlos de revisão, enquanto o agente se encarrega do raciocínio; não lhe cabe inventar o fluxo de trabalho à medida que avança.

Esta é a parte que não deve ser deixada a cargo dos agentes. Os agentes são probabilísticos; podem perder o controlo do estado, saltar um passo ou continuar com confiança partindo de um pressuposto errado. Quanto mais imprevisível se torna a camada de inteligência, mais valioso é ter uma estrutura determinística e monótona a rodeá-la.

Isto funciona?

Os resultados podem variar. 

Em março, a Anthropic publicou este artigo sobre agentes de longa duração, no qual apresentou um desafio com a seguinte instrução:

Criar um criador de jogos retro em 2D com funcionalidades que incluam um editor de níveis, um editor de sprites, comportamentos de entidades e um modo de teste jogável.

Experimentei fazer o mesmo com o Agentic Orchestrator, utilizando o modo totalmente autónomo — ou seja, sem necessidade de respostas por parte de pessoas. Após 12 horas, 250 dólares e sem qualquer ajuste adicional, este foi o resultado:

É verdade que, neste caso, se utilizou o Opus 4.7 em todas as fases, ao contrário do 4.5 utilizado pela Anthropic na sua demonstração. 

Isto começa a parecer-se bastante com um café expresso feito com a máquina Opus.

Então, já resolvemos a questão da engenharia de software?

Ainda não, mas, nesta altura, estamos bastante perto de resolver a questão da codificação.

Os modelos estão a melhorar a um ritmo que faz com que o planeamento trimestral pareça uma eternidade. Funcionalidades que hoje requerem cinco iterações de ciclo poderão ser concluídas amanhã numa única execução. Continuaremos a elevar o limite da janela de contexto, tornando o padrão de ciclo cada vez mais obsoleto. A etapa de revisão que deteta os erros de um agente perderá importância à medida que os agentes cometerem menos erros. Se tivermos sorte, o próprio Agentic Orchestrator ficará obsoleto dentro de algumas semanas, tornando-se desnecessário graças a modelos capazes de reter uma funcionalidade inteira na sua «cabeça», sem precisarem de um ficheiro de progresso para se lembrarem do que estavam a fazer. Sinceramente, esse seria o melhor resultado possível. Não o criei porque quisesse construir um orquestrador. Criei-o porque os agentes não supervisionados não eram suficientemente bons para funcionar sem um.

Mas mesmo num mundo em que os modelos sejam dez vezes mais capazes do que são hoje, há uma coisa de que estou cada vez mais convencido de que não vai mudar — o conhecimento humano da área e a nossa compreensão fundamental dos sistemas não podem ser substituídos.

Eis o que aprendi após semanas a trabalhar com este fluxo de trabalho: a qualidade do resultado final continua a depender do que se introduz. Os agentes não são mágicos. São amplificadores. Se lhes dermos uma instrução vaga, obtemos uma implementação vaga. Se não compreendermos a base de código suficientemente bem para responder às suas perguntas, eles farão suposições, e essas suposições estarão erradas.

A função do engenheiro de software está a mudar, não a desaparecer.

Quando a sua linguagem de programação é o inglês, as competências que importam mudam. Continua a ser necessário ser um bom engenheiro, possivelmente ainda mais do que antes, mas a natureza do trabalho muda. Há menos digitação e mais reflexão, menos sintaxe e mais arquitetura. Deixaremos de perguntar como implementar algo e começaremos a perguntar o que devemos implementar e porquê. O valor do engenheiro de software já não está em escrever código. Está em saber como é o código certo e ser capaz de o articular claramente para que um agente o possa produzir.

O modelo que acabei por adotar é mais ou menos assim: durante o dia, o ser humano faz o trabalho próprio de um ser humano. Escreves instruções. Respondes a perguntas. Analisas planos. Tomas decisões sobre casos-limite e compromissos. Depois, os agentes assumem o comando e implementam durante horas — durante a noite, na hora do almoço ou enquanto estás em reuniões. Quando voltas ao computador, podes rever o PR, ajustar o que for preciso, publicar e seguir em frente. O ser humano fornece a intenção e o discernimento. A máquina fornece o trabalho e a resistência. É a delegação na sua forma mais pura e, como toda a delegação, exige que quem delega saiba do que está realmente a falar.

Uma reflexão sobre a intensidade do trabalho, a dependência e a saúde mental

Quero mudar um pouco o tom aqui, porque esta parte é importante.

Há uma narrativa no discurso sobre a produtividade da IA que diz mais ou menos assim: «As ferramentas de IA libertam-lhe tempo para que se possa concentrar no trabalho criativo e de elevado valor.» Isto parece maravilhoso. Mas, na minha experiência, também está, em grande parte, errado.

Um estudo recente da BCG, realizado junto de 1 488 trabalhadores, revelou algo que teve um grande impacto: os auto-relatos de produtividade aumentavam quando se utilizavam entre uma a três ferramentas de IA, mas desciam drasticamente com quatro ou mais. Os trabalhadores referiram um maior esforço mental, maior fadiga mental e mais sobrecarga de informação quando a IA exigia uma supervisão mais intensa. A autora do estudo, Julie Bedard, da BCG, descreveu que os trabalhadores sentiam que estavam a «atingir os limites da sua capacidade cerebral».

E eu também senti isso. Eis o que realmente aconteceu quando consegui que o fluxo de trabalho «agente» funcionasse sem problemas. Não trabalhei menos horas. Trabalhei o mesmo número de horas, mas com uma densidade significativamente maior. Em vez de escrever código durante oito horas, passei o tempo a tomar decisões, a alternar entre funcionalidades, a rever implementações, a responder a perguntas dos agentes e a escrever instruções durante oito horas seguidas. O tempo de inatividade mecânica, a digitação, a depuração e o espaço para respirar que tinha enquanto escrevia o código padrão e refletia sobre o que estava a fazer desapareceram. Cada minuto envolvia a tomada de decisões. Cada minuto aumentava a minha carga cognitiva.

No final do dia, sentia-me mais cansado, e não menos. Estava mais esgotado, e não com mais energia. E há uma dependência subtil nisso: quando se consegue lançar uma funcionalidade antes de ir dormir e acordar com um comunicado de imprensa, a tentação de lançar três funcionalidades antes de ir dormir é avassaladora. Os agentes não se cansam. Mas continuas a ser o gargalo para todas as perguntas que fazem e todos os planos que propõem. O trabalho acumula-se e, se não tiveres cuidado, acaba por te esmagar também.

Não tenho uma solução clara para isto. Estamos num período de transição complicado, em que as ferramentas ultrapassaram a nossa capacidade de as utilizar de forma sustentável. Estamos todos a tentar perceber a ergonomia de uma forma de trabalhar fundamentalmente nova, e fingir que tudo são vantagens acaba por prejudicar todos aqueles que estão a tentar adaptar-se a esta nova realidade.

Faz pausas. Estabelece limites. Lembra-te de que os agentes ainda lá estarão de manhã. E se te apanhares a atualizar o painel de controlo à meia-noite para ver se a revisão foi aprovada, fecha o portátil. Digo isto por experiência própria. O comunicado de imprensa pode esperar.

Vai criar alguma coisa!

Comecei por dizer que sou, na melhor das hipóteses, um utilizador de nível intermédio. Mesmo depois de escrever tudo isto, continuo a acreditar nisso. A diferença é que agora conheço o perfil da curva de aprendizagem e posso dizer-vos: vale a pena escalá-la.

Eis o que diria a alguém que estivesse a começar do zero hoje: não tentes fazer o impossível. Começa com uma tarefa pequena no Cursor ou no Claude Code. Familiariza-te com o ritmo de introduzir prompts, rever e iterar. Depois, experimenta a estrutura RPI numa única funcionalidade — primeiro a investigação, depois o planeamento e, por fim, a implementação. Não precisas de nada sofisticado. Basta abrires três sessões do Claude, uma para cada fase, e passares os artefactos manualmente. Sente a diferença quando o agente tem contexto proveniente da sua própria pesquisa, em vez de um prompt escrito à pressa por ti.

Assim que isso fizer sentido — e vai fazer sentido —, começarás a perceber as lacunas que ferramentas melhores podem preencher. Talvez venhas a criar o teu próprio orquestrador. Talvez venhas a usar o meu. Talvez, quando leres isto, já exista algo melhor do que ambos. Não há problema. As estruturas e as ferramentas vão continuar a mudar. Os modelos mentais não, pelo menos não tão depressa. Investiga antes de planear. Planeie antes de implementar. Repita o processo quando ficar sem contexto. Deixe que o agente faça perguntas. E conheça a sua base de código suficientemente bem para lhes responder.

A máquina de café expresso ainda não é totalmente automática. Mas também já não é um café feito manualmente. Estamos algures na gama das máquinas semiautomáticas, e os cafés estão a ficar cada vez melhores a cada semana que passa.

Agora vai experimentar. E quando encontrares algo que funcione, partilha, porque garanto-te que há alguém que está exatamente na mesma situação em que estavas no mês passado.

A DoorDash funciona com base em vários microsserviços. Uma única solicitação de um utilizador pode necessitar de dados provenientes de vários serviços, tais como metadados das lojas, ementas, disponibilidade, preços e contexto de execução da encomenda. À medida que o tráfego da DoorDash crescia nos primórdios da história da empresa, muitos serviços solicitavam repetidamente os mesmos dados de baixa mutação em intervalos de tempo curtos.

Mesmo quando os dados não se alteravam, cada pedido continuava a desencadear chamadas gRPC, cálculos repetidos e, por vezes, leituras na base de dados do backend. À escala da DoorDash, isto gerava uma utilização desnecessária dos recursos do serviço, uma carga mais elevada na base de dados e um agravamento da latência P90/P99.

Estas chamadas repetidas também aumentaram o risco de fiabilidade. Quando uma dependência ficava lenta ou indisponível, as tentativas de repetição e o aumento da concorrência podiam espalhar a pressão pelos serviços dependentes, transformando um pequeno problema num incidente de maior dimensão.

As caches locais ajudaram, mas eram específicas de cada serviço, inconsistentes e exigiam que cada equipa criasse e mantivesse a sua própria lógica de cache. Precisávamos de uma camada de cache partilhada que pudesse reduzir o trabalho repetitivo, proteger os serviços a montante durante interrupções parciais e ser adotada sem alterações no código da aplicação.

Isso motivou-nos a criar o Entity Cache.

O que criámos

O Entity Cache é um proxy de cache HTTP/gRPC transparente que funciona no âmbito da malha de serviços baseada no Envoy da DoorDash. Inserido no caminho da solicitação, fornece respostas frequentemente acedidas a partir de um cache centralizado, sem que cada serviço tenha de implementar a sua própria lógica de cache.

Como o Entity Cache está integrado na malha de serviços, os serviços continuam a efetuar as mesmas chamadas HTTP ou gRPC de sempre. A integração é feita através da configuração da malha de serviços, pelo que não é necessário alterar o código do cliente nem do serviço a montante.

Por exemplo, um ponto final gRPC pode ser encaminhado através do Entity Cache com uma configuração como esta:

- name: example-service/grpc/50051
  redirect:
    enabled: true
    entity_cache: true
    endpoints:
      - path: ^/example.v1.ExampleService/GetExample$
        headers:
          - name: :method
            string_match:
              exact: POST
        rollout: 1

Ao fornecer respostas armazenadas em cache antes de os pedidos chegarem aos serviços a montante, o Entity Cache ajuda a DoorDash a:

  • Reduzir a latência evitando chamadas HTTP/gRPC repetidas entre serviços.
  • Reduzir a carga, eliminando cálculos repetidos e leituras da base de dados no backend.
  • Aumentar a fiabilidade ao fornecer dados em cache quando os serviços a montante estão lentos ou indisponíveis.
  • Custos de infraestrutura mais baixos através da redução da utilização desnecessária de recursos de computação e de bases de dados.
  • Reduzir o esforço de engenharia ao disponibilizar uma cache centralizada, em vez de exigir que cada serviço crie a sua própria camada de cache.

Desde o seu lançamento, o Entity Cache foi implementado em muitos terminais de Nível 0 e Nível 1 com baixa taxa de mutação. Atualmente, processa mais de 1,5 milhões de pedidos por segundo, atinge taxas de acerto no cache superiores a 90% em muitos terminais e tornou-se uma camada de desempenho e fiabilidade à escala da plataforma, construída com base na malha de serviços da DoorDash.

Visão geral da arquitetura de alto nível

O Entity Cache é implementado como um proxy de cache dedicado que se integra à nossa malha de serviços baseada no Envoy. Os serviços dos clientes não alteram o seu código. Conforme ilustrado na Figura 1, a malha de serviços encaminha automaticamente as solicitações de saída primeiro através do Entity Cache, com o serviço a montante configurado como alternativa. Quando chega uma solicitação, o Entity Cache verifica se existe uma resposta válida em cache. Se existir, essa resposta é devolvida imediatamente; os erros de cache são encaminhados para o serviço a montante, que gera uma resposta que é, por sua vez, armazenada em cache para futuras solicitações.

Figura 1: O Entity Cache situa-se entre a malha de serviços e os serviços a montante, fornecendo respostas armazenadas em cache provenientes do Valkey em caso de correspondência e reencaminhando as respostas sem correspondência para os serviços a montante, para a obtenção de dados atualizados. Os eventos de invalidação do Kafka e o Cache Advisor fornecem sinais de atualização e de integração, de modo a que o armazenamento em cache seja seguro, transparente e gerido de forma centralizada.
  • Caminho da solicitação: O Envoy encaminha as solicitações primeiro para o Entity Cache, recorrendo ao serviço a montante, se necessário. Em caso de acerto no cache, o Entity Cache fornece a resposta diretamente a partir do Valkey, contornando as dependências a montante. Em caso de falha no cache, encaminha a solicitação para o serviço a montante, armazena a resposta no Valkey de acordo com a política de cache e devolve-a ao cliente.
  • Percurso de invalidação da cache: Quando os dados subjacentes são alterados, os serviços a montante emitem eventos de alteração como parte dos seus fluxos de trabalho normais de gravação. Estes eventos passam pelo Kafka até ao Entity Cache. Em vez de eliminar as entradas armazenadas em cache em todas as instâncias, o Entity Cache regista que uma entidade específica foi atualizada e anota a hora da atualização. Em cada pedido, o Entity Cache compara a hora em que a resposta foi armazenada em cache com a hora de atualização registada. Se a resposta em cache for mais antiga do que a hora de atualização, é considerada desatualizada. O Entity Cache obtém então dados atualizados do serviço a montante e atualiza o cache. Esta abordagem evita uma eliminação distribuída complexa, garantindo ao mesmo tempo que os dados atualizados sejam refletidos rapidamente.
  • Percurso de análise de tráfego: O Entity Cache valida continuamente a exatidão utilizando amostras do tráfego de produção. Para um subconjunto de pedidos, compara as respostas armazenadas em cache com as respostas em tempo real do servidor de origem e calcula as divergências em segundo plano. Este mecanismo de validação garante que o armazenamento em cache se mantém preciso ao longo do tempo e proporciona uma base segura para implementações controladas. Para apoiar este processo, criámos o Cache Advisor, um serviço independente dedicado a identificar pontos finais com baixas taxas de mutação, que são fortes candidatos ao armazenamento em cache. Este serviço funciona de forma independente na entrada do servidor de origem, utilizando o filtro de processamento externo do Envoy para observar padrões de pedidos e identificar estas oportunidades.
  • Failover automático: O Envoy recorre automaticamente ao serviço a montante caso o Entity Cache fique indisponível.

Funcionalidades implementadas

O Entity Cache não é uma única otimização, mas sim um conjunto de funcionalidades centradas na fiabilidade e no desempenho, concebidas para funcionar em conjunto à escala da DoorDash. Esta secção destaca os mecanismos fundamentais que tornam o armazenamento em cache seguro, previsível e eficaz em produção.

1. Degradação gradual com TTLs duplos: Conforme ilustrado na Figura 2, o Entity Cache utiliza dois limiares de expiração: um tempo de vida (TTL) «suave», que define a atualidade — por exemplo, 60 segundos — e um TTL «rígido», que define a idade máxima absoluta — por exemplo, cinco minutos. Em condições normais, os dados armazenados em cache são atualizados de acordo com a definição do TTL flexível. Quando os serviços a montante ficam lentos ou indisponíveis, o Entity Cache continua a fornecer dados ligeiramente desatualizados a partir do cache, em vez de propagar erros aos clientes.

Figura 2: O Entity Cache utiliza a antiguidade dos dados para decidir se deve servir a partir da cache, atualizar a partir da fonte ou solicitar dados atualizados. A principal conclusão é que os TTLs flexíveis permitem o serviço de dados desatualizados de forma suave quando a fonte está lenta, enquanto os TTLs rígidos obrigam a uma nova obtenção de dados após o prazo máximo de validade.

Este design revelou-se fundamental durante uma interrupção de várias horas a montante, quando o Entity Cache continuou a fornecer dados em cache desatualizados, mas válidos, em vez de entrar em falha, evitando assim um incidente que afetasse toda a plataforma.

2. Invalidação do cache em tempo real com listas de exclusão: a eliminação direta numa frota de proxies distribuída exigiria a reconstrução de chaves de cache, a coordenação entre pods e o tratamento de condições de corrida, em que dados desatualizados poderiam ser regravados imediatamente após a remoção. Além disso, introduz uma sobrecarga de memória para o acompanhamento das associações de chaves. A abordagem baseada em carimbos de data/hora evita a coordenação entre pods e mantém a correção mesmo que os eventos cheguem fora de ordem.

Tradicionalmente, o armazenamento em cache obriga a um compromisso entre TTLs longos, para melhorar o desempenho, e TTLs curtos, para garantir a consistência. O Entity Cache resolve esta questão através da invalidação orientada por eventos, utilizando uma lista de exclusão. Quando os dados subjacentes se alteram, os serviços de origem publicam um evento de invalidação no Kafka. Um consumidor do Entity Cache regista um carimbo de data e hora de invalidação no Valkey para a entidade e os identificadores afetados. Numa correspondência no cache, o Entity Cache verifica esta entrada. Se o objeto armazenado em cache tiver sido recuperado antes do carimbo de data/hora de invalidação, é tratado como desatualizado e atualizado a partir do upstream. Em vez de eliminar chaves do cache, o sistema baseia-se na comparação de carimbos de data/hora. Cada objeto armazenado em cache guarda a sua hora de recuperação; a invalidação limita-se a registar um carimbo de data e hora mais recente. Este design permite que os pontos finais mantenham TTLs flexíveis de longa duração para altas taxas de acertos, ao mesmo tempo que alcançam consistência quase em tempo real, com uma latência de invalidação P99 almejada de cerca de um segundo.

3. Detecção de valores atípicos baseada no Envoy — failover automático: O Entity Cache integra-se com a nossa malha de serviços baseada no Envoy. Conforme ilustrado na Figura 3, o Envoy monitoriza o estado dos pods de cache e exclui automaticamente os pods com estado anómalo do conjunto de balanceamento de carga. Se todos os pods de cache ficarem com estado anómalo, o Envoy encaminha o tráfego diretamente para os serviços a montante, garantindo a disponibilidade mesmo durante falhas totais do cache.

Figura 3: A malha de serviços encaminha o tráfego normal para pods do Entity Cache em bom estado e exclui os pods com falhas do percurso de equilíbrio de carga. Se o percurso do cache não estiver disponível, o tráfego é totalmente redirecionado para o serviço a montante, preservando a disponibilidade.

Este modelo de fallback automático significa que o Entity Cache aumenta a resiliência sem introduzir um novo ponto único de falha. Durante a integração, se uma configuração incorreta provocar a falha dos pods de cache, o tráfego recorre automaticamente ao servidor a montante. As equipas podem ativar o cache com confiança.

Escalabilidade para milhões de pedidos por segundo

O facto de processarmos milhões de pedidos por segundo criou estrangulamentos ao nível do sistema, nos quais pequenas ineficiências se amplificaram rapidamente. A alocação de memória, a coordenação de pedidos e o comportamento da cache começaram a afetar a latência de cauda e a estabilidade a montante. Para manter o desempenho, concentrámo-nos em reduzir a contenção, suavizar os padrões de tráfego e eliminar o trabalho redundante nos percursos mais utilizados.

  • Gestão de memória em grande escala: as alocações frequentes nos percursos mais ativos aumentaram a pressão sobre a recolha de lixo (GC) e provocaram picos de latência. Implementámos um sistema personalizado de agrupamento de buffers para reutilizar a memória e minimizar as alocações. Isto reduziu a sobrecarga da GC, estabilizou a utilização da memória e diminuiu a latência de sobrecarga das solicitações P99 para cerca de 2,1 ms, mantendo simultaneamente um débito consistente.
  • Avalanche de cache em grande escala: No caso de entidades muito solicitadas, a expiração baseada no TTL concentrava o tráfego de atualização num único momento, criando picos na carga a montante. Implementámos uma atualização antecipada probabilística com base no algoritmo XFetch. À medida que as entradas se aproximam do seu TTL flexível, cada pedido tem uma probabilidade crescente de desencadear uma atualização. Isto distribui o tráfego ao longo do tempo, reduzindo os picos e estabilizando a carga a montante.
  • Concorrência à escala de milhões de pedidos: os pedidos simultâneos muitas vezes duplicavam o mesmo trabalho, especialmente no que diz respeito ao estabelecimento de ligações e a falhas de cache em chaves mais utilizadas. Introduzimos um padrão de «voo único» utilizando estruturas atómicas e sem bloqueios. O «compare and swap» garante que apenas uma goroutine efetua uma ligação ou uma recuperação por chave, enquanto as outras reutilizam o resultado. A deduplicação ocorre por pod, sem coordenação entre pods. Isto eliminou o trabalho redundante e reduziu a contenção. O débito por pod aumentou cerca de cinco vezes, as taxas de alocação diminuíram entre 50% e 60% e os picos de latência P99 foram reduzidos em até 80%.
  • Identificar o que armazenar em cache: Com milhares de pontos finais, identificar manualmente os melhores candidatos para armazenamento em cache não era uma solução escalável. 

Criámos o Cache Advisor para analisar o tráfego real de produção e orientar a implementação. Executado na entrada através do filtro de processamento externo do Envoy, este ferramenta recolhe amostras de pedidos, monitoriza a estabilidade das respostas e estima a frequência de atualização. Recomenda valores de TTL e apresenta as melhores opções. Isto permitiu identificar mais de 130 oportunidades de implementação, possibilitando uma adoção mais ampla e segura do cache.

Impacto nos utilizadores/na empresa

O Entity Cache tornou-se uma camada essencial de fiabilidade na DoorDash, atendendo atualmente a mais de 1,5 milhões de pedidos por segundo com 99,99999% de disponibilidade. Foram integrados mais de 100 pontos de extremidade em 50 serviços, proporcionando melhorias mensuráveis em termos de latência, escalabilidade e resiliência a falhas.

Do ponto de vista do desempenho, o Entity Cache acrescenta uma sobrecarga mínima, proporcionando, ao mesmo tempo, ganhos substanciais:

  • Latência de overhead de um pedido P99 de aproximadamente 2,1 ms
  • Taxa de acertos na cache consistentemente superior a 90%
  • Redução de 60% a 95% nas solicitações a montante durante o funcionamento normal
  • Redução da latência até 90% para terminais recém-integrados
  • Mesmo no caso de serviços com caches locais, a latência de ponta a ponta é frequentemente reduzida em cerca de metade, devido ao menor número de chamadas de rede entre serviços na saída do cliente

Para além do desempenho, o Entity Cache reforçou significativamente a fiabilidade da plataforma. Durante vários incidentes em produção, impediu que até 90% dos pedidos chegassem a serviços a montante com desempenho reduzido. Ao fornecer dados desatualizados, mas válidos, quando necessário, protegeu os serviços de interrupções a montante, eliminou a amplificação causada por pânico através da atualização probabilística e reduziu o alcance dos picos de tráfego.

A monitorização contínua das divergências permite uma adoção segura, dando às equipas a confiança necessária para ativar o armazenamento em cache sem comprometer a exatidão. Embora a redução dos custos de infraestrutura seja um efeito secundário, a diminuição da carga a montante também reduziu os requisitos de aprovisionamento e melhorou a eficiência global em toda a plataforma.

Próximas etapas

  1. Divisão dinâmica do tráfego com base na comparação da taxa de erro: Atualmente, a divisão do tráfego entre o Entity Cache e o servidor de origem baseia-se numa configuração estática. Estamos a desenvolver um sistema de redirecionamento dinâmico do tráfego que ajusta o encaminhamento com base em comparações da taxa de erro em tempo real, enviando mais tráfego para o servidor de origem quando o estado do cache se deteriora e revertendo essa mudança à medida que o estado melhora.
  2. Isolamento de «vizinhos ruidosos» por cluster de cache a montante: Atualmente , todos os serviços partilham um único cluster de cache. Estamos a avançar para a implementação de clusters de cache dedicados por serviço de elevado tráfego, a fim de estabelecer limites rígidos de recursos, evitando que problemas num serviço afetem os outros.
  3. Limitação adaptativa da concorrência: os limites estáticos de concorrência não são ideais, pois ou desperdiçam capacidade em períodos de baixa carga ou provocam picos de latência em períodos de alta carga. Estamos a implementar uma limitação adaptativa que mede continuamente o desempenho do sistema e ajusta automaticamente a concorrência com base na latência e no débito observados, maximizando a capacidade e mantendo simultaneamente os objetivos de latência.

Agradecimentos

A criação do Entity Cache foi um esforço colaborativo entre várias equipas da DoorDash. Gostaríamos de destacar especialmente a equipa de Infraestrutura Central: Dakota Baber, Hochuen Wong, Yifan Yang e Tejas Lodaya, cujas contribuições foram inestimáveis para a construção, operação e expansão da plataforma.

Agradecemos também a Ivar Lazzaro, Muneeb Ansari, Karthik Katooru, Pushkar Raste, Matt Ranney, Thai Pham, Allen Meng, Madhav Gali, Jay Weinstein, Matt Zimmerman e Sebastian Yates pela orientação, comentários e apoio ao longo de todo o projeto.