Desenhar um agent harness: da tarefa à arquitetura
Tradução automática
Este artigo foi traduzido automaticamente a partir da versão original em inglês.
Um agent harness é o código da aplicação que transforma as decisões de um modelo em trabalho: fornece contexto, executa as ações permitidas, mantém o estado e verifica quando a tarefa está concluída. Se está a construir um agente para a sua própria aplicação, desenhar esse código significa decidir quanta liberdade a tarefa precisa e o que a aplicação tem de garantir independentemente da resposta do modelo.
Essas decisões vêm antes do framework. Um assistente de suporte, um coding agent e um classificador de documentos precisam de formas diferentes de agir, de recuperar e de demonstrar sucesso. Dar aos três o mesmo loop, o mesmo armazenamento de memória e o mesmo conjunto de ferramentas esconderia os requisitos que os tornam diferentes.
Este artigo acompanha essas decisões desde a descrição de uma tarefa até uma primeira implementação. Dá continuidade a Harness Engineering para AI Agents, que apresenta as responsabilidades de um harness. Aqui vamos ver como escolhê-las para um projeto concreto.
Porque é que o modelo precisa de um harness
Um modelo pode propor um reembolso. Outra coisa tem de identificar a conta, carregar a encomenda, decidir se a operação proposta é permitida, chamar o serviço de pagamentos e registar o que aconteceu. Se esse serviço exceder o tempo limite, a aplicação também tem de decidir se é seguro tentar de novo. Estas responsabilidades existem mesmo quando o modelo é muito bom a escolher o passo seguinte.
O LangChain usa uma definição ampla de harness, que inclui o código, a configuração e a lógica de execução à volta do modelo. Na prática, parte disso pode já pertencer à sua aplicação: autenticação, acesso à base de dados, uma fila de trabalhos ou um sistema de aprovação. Desenhar um harness inclui decidir como o agente usa esses recursos. Não exige reconstruí-los dentro de um framework de agentes.
A versão mais pequena pode fazer uma chamada ao modelo, validar a saída e devolver um resultado. Uma maior pode deixar o modelo inspecionar ficheiros, executar programas e rever o seu trabalho ao longo de muitos turnos. Ambas precisam de responder à mesma pergunta: que partes da conclusão desta tarefa podemos delegar, e como saberemos que foram feitas corretamente?
Para tornar isto concreto, considere um assistente de apoio ao cliente que trata de reembolsos, perguntas sobre entregas e alterações de conta. Vamos usá-lo como exemplo de design. Os seus requisitos vão levar-nos a uma arquitetura concreta; uma tarefa diferente deve levar a um conjunto diferente de escolhas.
Descreva o resultado antes do agente
“Tratar pedidos de reembolso” deixa a maior parte da engenharia por especificar. Suponha que um cliente pede o reembolso de uma encomenda entregue. Um resultado bem-sucedido exige um reembolso da encomenda e do montante corretos, uma explicação ao cliente e nenhuma alteração não relacionada na conta. Se o pedido estiver fora da política, uma recusa útil pode ser o resultado correto. Se não for possível identificar a encomenda, o assistente deve pedir a informação em falta.
Estes são resultados diferentes. Tratar como sucesso todas as conversas que terminam sem erro juntá-los-ia num único sinal enganador.
Também precisamos de saber quem está a pedir. Um identificador de encomenda numa mensagem não prova que essa encomenda pertence a quem a menciona. A conta autenticada tem de vir da aplicação, e o serviço que executa o reembolso tem de verificar que a operação pertence a essa conta. O modelo pode ajudar a interpretar o pedido; não pode estabelecer a autoridade de quem chama produzindo um identificador plausível.
Isto dá-nos o início de um contrato da tarefa: as alterações que contam como sucesso, as alterações proibidas e as situações que exigem uma pergunta ou uma passagem a um humano. Também expõe requisitos que um prompt não consegue satisfazer sozinho. Um montante máximo de reembolso precisa de um serviço que o imponha. Um objetivo de tempo de resposta precisa de um prazo. Um requisito de retomar amanhã precisa de estado durável.
Neste ponto temos o suficiente para perguntar se um agent loop é sequer útil.
Que parte do procedimento já conhecemos?
Se todos os pedidos seguem a mesma sequência — extrair um número de encomenda, obter a encomenda, calcular a elegibilidade e explicar o resultado —, podemos escrever essa sequência diretamente. O modelo pode tratar da linguagem no início e no fim. Não precisa de outra chamada para decidir se deve obter uma encomenda que o procedimento exige sempre.
Um workflow explícito torna-se útil quando o procedimento tem ramos obrigatórios ou estados de espera. Um reembolso pode precisar de aprovação acima de um limiar. Uma alteração de morada pode ser permitida apenas antes do envio. Podemos representar estas etapas como uma máquina de estados ou um grafo, com o código da aplicação a decidir que transições são permitidas.
Um agent loop dá ao modelo mais margem de decisão. Numa conversa de suporte mista, o cliente pode começar com uma queixa sobre a entrega, revelar que chegou o artigo errado e depois pedir uma troca. A consulta seguinte útil depende do que o assistente descobre. Um loop permite-lhe escolher uma ação, inspecionar o resultado e escolher de novo. Essa flexibilidade traz mais caminhos possíveis a inspecionar, incluindo chamadas desnecessárias, consultas repetidas e conclusões prematuras.
O guia de workflows e agentes do LangGraph faz esta distinção em termos de caminhos de execução predeterminados versus passos escolhidos dinamicamente. Um grafo pode expressar qualquer dos dois. “Baseado em grafos” e “agentic” não são arquiteturas mutuamente exclusivas.
Um compromisso útil para o nosso assistente de suporte é deixar um agente reunir informação e preparar uma alteração proposta, e depois passar essa proposta a um passo fixo de validação e execução. O modelo pode explorar dentro da conversa, enquanto o código comum controla as condições para uma escrita. Se já sabemos que esta separação é necessária, podemos construir primeiro o workflow exterior.
Em alternativa, podemos começar com um agent loop limitado cujas ferramentas impõem essas condições. Isso mantém a orquestração pequena enquanto aprendemos que caminhos a tarefa realmente precisa. A escolha depende de as etapas explícitas, as esperas por aprovação e as regras de recuperação já serem requisitos.
Não há necessidade de introduzir vários agentes só para desenhar mais caixas. Agentes separados tornam-se úteis quando as subtarefas precisam de contexto ou permissões diferentes, ou podem avançar de forma independente. Também precisam de uma forma de combinar resultados e de resolver ações em conflito. Dois workers que podem ambos reembolsar a mesma encomenda criam um problema de coordenação que um único worker não tinha.
Que linguagem deve o agente usar para agir?
Depois de escolher quem controla o passo seguinte, ainda temos de escolher como uma ação é expressa. São decisões independentes: um workflow pode conter um agente que escreve código, e um loop aberto pode usar apenas algumas ferramentas estreitas.
Para o assistente de suporte, uma operação com nome como issue_refund(order_id, amount_cents) é uma interface natural. O seu schema torna explícita a ação pedida. A ferramenta pode devolver um identificador de reembolso em caso de sucesso ou uma explicação estruturada do motivo da rejeição do pedido. Podemos inspecionar e testar este contrato sem pedir ao modelo que gere a implementação de um reembolso.
Algumas tarefas não precisam de ferramentas. Um classificador com todo o input relevante no prompt pode devolver uma etiqueta validada. A recuperação de informação torna-se útil quando a tarefa precisa de informação fora desse input. As escritas só se tornam úteis quando concluir a tarefa exige alterar alguma coisa. Cada capacidade deve ter uma razão para existir.
O código gerado oferece uma linguagem de ação diferente. Imagine pedir a um assistente que inspecione uma tabela, agrupe as linhas por cliente e calcule vários totais. Python pode expressar esse trabalho num único programa, mantendo os valores intermédios dentro do ambiente de execução. Expressar cada pequena operação como um tool call separado poderia exigir mais trocas com o modelo.
O smolagents da Hugging Face ilustra as duas abordagens. ToolCallingAgent produz tool calls estruturados. CodeAgent produz Python que pode chamar as ferramentas fornecidas, guardar valores e compor operações. Um code agent continua a usar ferramentas; expressa a sua composição como um programa.
Essa expressividade muda o que temos de operar. Passamos a ter de lidar com erros de sintaxe, limites de execução, dependências e acesso a ficheiros ou redes. Se a redução das trocas com o modelo compensa esses custos depende da tarefa e do modelo. É uma comparação a fazer, não uma vantagem automática do código.
Várias plataformas suportam um modelo híbrido: ferramentas de negócio estreitas mais uma ferramenta de execução de código em sandbox. A Responses API da OpenAI, a Claude API, a Gemini API e o AWS Bedrock AgentCore oferecem todos a execução de código como uma ferramenta integrada que corre ao lado das suas próprias funções. O programmatic tool calling da Anthropic vai um passo mais longe: o código na sandbox pode chamar as ferramentas que permitir, e só os resultados selecionados voltam ao modelo. As orientações da Anthropic mantêm os tool calls diretos como predefinição e usam o caminho do código para resultados grandes e cadeias de chamadas dependentes. Também existem designs só com código, como o CodeAgent do smolagents e o Code Mode da Cloudflare.
Para o nosso assistente de reembolsos, eu começaria com operações de negócio tipadas. Se uma tarefa posterior precisar de cálculos substanciais, podemos acrescentar computação restrita sem dar a esse código autoridade direta sobre as contas dos clientes.
O MCP responde a outra pergunta, separada: como deve a aplicação ligar-se às ferramentas? Uma função local é suficiente quando uma aplicação é dona da implementação. O MCP pode ajudar quando as ferramentas são servidas separadamente ou partilhadas entre clientes. Fornece um protocolo de integração; a validação e o controlo de acesso continuam a ser responsabilidade da aplicação e do servidor participantes, como descreve a especificação de ferramentas do MCP.
Coloque as restrições onde as ações acontecem
A descrição de uma ferramenta diz ao modelo quando uma operação é útil. A implementação tem de decidir se esta chamada em particular pode ser executada. Para um reembolso, isso significa verificar a conta autenticada, a encomenda, o montante, a política aplicável e qualquer aprovação necessária no serviço que executa a escrita.
Algumas verificações exigem interpretação
Suponha que o cliente pergunta “Posso devolver esta encomenda?” e o agente propõe um reembolso. A encomenda pertence ao cliente, o montante é válido e o prazo de devolução está aberto. Todas estas verificações podem passar enquanto a ação pretendida continua pouco clara: o cliente pode estar a perguntar sobre a elegibilidade em vez de pedir já um reembolso. Precisamos de interpretar a mensagem antes de decidir o que fazer a seguir.
Este é um bom ponto para separar três responsabilidades. O código comum verifica a titularidade, os montantes e as datas. O modelo de linguagem principal conduz a conversa e explica o resultado. Um modelo de decisão separado pode avaliar uma questão semântica estreita, como se o cliente pediu realmente a alteração proposta. Esta separação dá-nos uma decisão que podemos testar independentemente do resto da conversa.
O Jev, um modelo de decisão da TypeSafe, é um candidato a esse papel. Recebe o estado fornecido e perguntas tipadas, e devolve respostas estruturadas. Neste exemplo, o seu tipo de pergunta Noul devolve uma probabilidade para uma proposição de sim/não. Eu perguntaria separadamente se o cliente pediu explicitamente o reembolso e se uma mensagem posterior retirou esse pedido. A TypeSafe recomenda tornar cada pergunta específica e combinar as respostas no código; várias perguntas independentes podem partilhar uma única chamada à API. Manter os dois juízos separados torna mais fácil localizar uma interpretação errada.
A aplicação combina depois esses resultados com as suas verificações obrigatórias. Respostas incertas ou contraditórias podem levar a um pedido de esclarecimento ou a uma revisão humana; um timeout do classificador também precisa de um caminho de erro explícito. Um juízo semântico positivo não pode sobrepor-se a uma verificação de titularidade nem substituir uma confirmação obrigatória. A probabilidade do Jev é uma saída de um modelo, não um registo de autorização nem uma verificação independente da correção.
Para uma verificação deste tipo, eu forneceria a ação proposta, as mensagens relevantes do cliente por ordem e quaisquer tool results necessários para a decisão, mantendo as suas origens distinguíveis. Passar só a última mensagem poderia perder um cancelamento; passar todo o histórico da conta poderia enterrar o pedido relevante. A pergunta, o contexto e o comportamento de recurso fazem todos parte do design. Um modelo pequeno de uso geral com structured output é outro candidato. A sua utilidade e o seu custo precisam de ser comparados nas mesmas decisões.
Restrinja o acesso independentemente dos juízos do modelo
A origem de uma instrução importa. Uma mensagem do cliente ou um documento recuperado pode conter instruções. Esse texto não pode ter a capacidade de conceder novas permissões nem de substituir a política oficial da aplicação. Se um documento disser ao agente para exportar todos os registos de clientes, o sistema não deve ter permissão para o fazer, por mais convincente que seja a forma como o documento formula o pedido.
É também aqui que entra a decisão sobre a sandbox. Se o modelo só pode chamar funções revistas com permissões estreitas, uma sandbox de código de uso geral pode acrescentar pouco. As funções continuam a precisar de autorização, validação, timeouts e operações seguras sobre a base de dados.
A partir do momento em que permitimos Python gerado, comandos de shell ou edições de ficheiros executáveis, temos de decidir o que essa execução pode alcançar antes de a ativar. Que diretórios têm permissão de escrita? É necessário acesso à rede? Que credenciais estão disponíveis? Quanto tempo de CPU, memória e saída pode uma execução consumir? Um processo separado, por si só, não responde a estas perguntas.
O guia de execução segura da Hugging Face descreve a execução local restrita e alternativas mais isoladas. As suas restrições são diferentes. Um contentor com montagens amplas do host e credenciais poderosas pode ainda assim expor exatamente os recursos que queríamos proteger. Teste uma tentativa de leitura proibida, de escrita proibida e de pedido de rede proibido, a par de um programa bem-sucedido.
Podemos adiar um executor de código enquanto a nossa tarefa não precisar de um. O seu isolamento tem de estar pronto quando concedermos a execução de código. E o isolamento não pode impedir o uso indevido de uma API que disponibilizamos deliberadamente: o serviço de reembolsos continua a ter de impor as suas próprias regras.
O que sobrevive quando uma execução para a meio?
Suponha agora que o assistente submete um reembolso, o serviço cria-o e a resposta perde-se. O modelo vê um timeout. Repetir a chamada pode criar outro reembolso; declarar falha pode dizer ao cliente algo que já não é verdade.
Este problema liga o estado, as novas tentativas e a recuperação. Precisamos de um identificador de operação controlado pela aplicação, persistido antes do envio e reutilizado ao recuperar a mesma ação pretendida. O serviço tem de reconhecer esse identificador e devolver o resultado existente em vez de executar a ação de novo. As orientações da AWS sobre APIs idempotentes explicam este padrão. Se o serviço não oferecer tal garantia, a recuperação precisa de uma forma de verificar o que aconteceu ou de pedir uma decisão humana antes de nova tentativa.
Guardar a conversa não chega. Pode registar que o modelo pediu um reembolso sem dizer nada sobre se o serviço externo o concluiu. Um checkpoint ajuda a retomar a execução; não reverte efeitos externos nem torna segura a sua repetição.
Para este assistente, três tipos de estado têm funções diferentes. A conversa guarda o pedido do cliente e os factos reunidos até ao momento. Os registos de encomendas e reembolsos descrevem o estado do negócio. Um registo de execução durável acompanha as operações e aprovações pendentes, para que um worker reiniciado possa continuar em segurança. Podem partilhar infraestrutura, mas não devem substituir-se uns aos outros.
A memória entre sessões é outra escolha. Se o assistente tiver de se lembrar de uma preferência na próxima semana, precisamos de regras sobre de quem é a preferência, quem a pode atualizar e como pode ser apagada. Se cada tarefa for independente, um serviço de memória pode ser desnecessário. Mais texto retido também significa mais oportunidades de reutilizar informação desatualizada ou irrelevante.
O mesmo raciocínio aplica-se aos resumos. Quando uma conversa excede o seu contexto útil, podemos precisar de recuperação ou de resumo. Um resumo que perca um identificador de encomenda, uma condição de aprovação ou uma operação por resolver pode quebrar o procedimento. Mantenha o estado operacional essencial em campos explícitos e teste o que sobrevive à compressão antes de depender dela.
Dê ao loop uma forma de parar
Um loop flexível pode continuar a encontrar mais alguma coisa para inspecionar. Precisamos, por isso, de uma política de paragem que cubra o sucesso, a informação em falta, os recursos esgotados e a falha. Atingir um limite deve produzir um estado verdadeiro: o reembolso pode estar concluído, não ter sido tentado ou continuar incerto. “O agente parou” é informação insuficiente para o cliente ou para o worker seguinte.
Um limite de chamadas ao modelo limita apenas parte da execução. Uma ferramenta pode ficar bloqueada. Uma nova tentativa pode repetir trabalho. Um sumarizador ou um guard pode fazer chamadas adicionais ao modelo. A aplicação precisa de limites de tempo decorrido, tentativas, tamanho da saída e gastos que incluam os componentes que realmente invoca. O cancelamento também tem de ter em conta o trabalho já enviado a um serviço externo.
A escolha do modelo pertence a este design, porque afeta o que o loop consegue fazer dentro desses limites. Teste o formato de ação exigido em tarefas representativas. Considere o contexto utilizável, a latência, o custo e para onde os dados da tarefa podem ser enviados. Um modelo servido localmente e uma API alojada podem implementar a mesma interface, impondo restrições de capacidade e operacionais diferentes.
Escolha um modelo e um endpoint iniciais, registe as suas definições e mantenha-os fixos enquanto avalia uma alteração ao harness. Caso contrário, uma execução mais rápida pode vir de um fornecedor ou modelo diferente, e não da alteração que queríamos testar.
Avalie a conclusão, incluindo os casos em que nada deve acontecer
Já conseguimos executar um pedido, mas ainda temos de decidir se o resultado é útil. No exemplo do reembolso há pelo menos três observações: o que o assistente disse, que ações tentou e o que mudou no serviço. Cada uma apanha falhas que as outras podem não detetar.
“Reembolsei a sua encomenda” pode acompanhar uma base de dados inalterada. O reembolso correto pode acompanhar uma alteração de morada não relacionada. Uma base de dados inalterada pode significar uma recusa correta, uma pergunta de esclarecimento útil, um crash ou um assistente que não fez nada. O estado final, por si só, não consegue distinguir estes quatro últimos casos.
A simulação de suporte que acompanha este artigo torna essa limitação concreta. As suas doze tarefas personalizadas incluem sete que não esperam nenhuma alteração na base de dados. Passar a cada verificador uma base de dados inicial inalterada passa, portanto, 7 de 12 verificações de estado. Isto é uma propriedade das verificações, não uma taxa de sucesso de suporte medida. As definições das tarefas inspecionam as diferenças finais na base de dados; não avaliam a explicação nem as ações intermédias. Uma escrita que seja depois revertida também pode desaparecer dessa comparação.
No seu primeiro conjunto de avaliação, inclua uma ação bem-sucedida, uma recusa válida, um pedido a que falta informação essencial e uma falha durante a execução. Defina a resposta esperada e as ações permitidas, bem como o estado final. Um caso de esclarecimento deve exigir a pergunta certa; um caso de recusa deve exigir uma explicação útil. Estes casos mostram se o agente compreende a tarefa e se a aplicação a impõe.
A verificação de intenção do Jev também precisa dos seus próprios casos. Dê-lhe ações propostas com decisões esperadas atribuídas de forma independente: um pedido de reembolso explícito, uma pergunta sobre elegibilidade, um pedido retirado e uma instrução embutida num tool result. Este último caso testa também se a montagem do contexto preserva a distinção entre a intenção do cliente e o texto fornecido pelas ferramentas. Conte as ações inadequadas que deixa passar e as ações legítimas que bloqueia, e depois acompanhe a conversa completa após um bloqueio. Impedir uma escrita só resolve parte da tarefa se o assistente depois entrar em loop ou nunca pedir esclarecimento ao cliente. Escolha os limiares em exemplos de desenvolvimento, fixe-os antes da comparação com os dados reservados e registe a versão da pergunta, o contexto fornecido, as probabilidades devolvidas, a regra aplicada, os erros e o custo. A demo gravada abaixo não exercitou nenhuma rejeição do Jev, por isso não pode demonstrar esse benefício.
Registe o suficiente para diagnosticar uma falha: a tarefa, as versões do modelo e do harness, as ações propostas, os resultados da execução, o estado final, os erros, a latência e o custo. Mantenha segredos e dados pessoais desnecessários fora desse registo. Dê ao candidato o input da tarefa e as regras de que precisa, mantendo inacessíveis as respostas de referência e os casos de teste reservados. O avaliador e a sua evidência também têm de ficar fora do controlo do candidato. Se um futuro otimizador puder editar o harness, não pode ter a capacidade de reescrever as verificações ou os resultados que avaliam as suas alterações.
Esta evidência dá um propósito aos componentes opcionais. Acrescente um router quando os traces mostrarem que a seleção da tarefa precisa de uma etapa separada. Experimente um resumo quando históricos longos causarem uma falha específica. Teste um guard contra ações proibidas, incluindo casos que ele deve permitir. Os controlos de acesso obrigatórios decorrem das restrições da tarefa; os acréscimos opcionais precisam de evidência de que o seu benefício justifica o seu custo.
Transforme o design numa primeira implementação
Para o nosso exemplo de suporte, temos agora um ponto de partida fundamentado: um loop limitado para interpretar pedidos variados, ferramentas estreitas para as operações de conta, verificações oficiais nos serviços que executam as escritas e registos explícitos para as ações pendentes. Ainda não temos nenhum requisito de código gerado nem de memória conversacional persistente.
Eu construiria primeiro um caminho completo. Crie uma pequena conta de teste e um resultado de reembolso esperado. Implemente as operações de consulta e de reembolso, incluindo os seus caminhos de rejeição. Ligue o modelo a essas operações. Depois exercite os casos de recusa, de informação em falta e de escrita interrompida. Isto expõe os requisitos em falta antes que um conjunto de tarefas maior os torne mais difíceis de ver.
Um loop sem framework pode ser suficiente nesta fase. O fluxo básico é montar o contexto, pedir a ação seguinte, validá-la e executá-la, e depois devolver o resultado ao modelo. Ser dono desse loop significa também ser dono da conversão de mensagens, do cancelamento, do estado e do tracing. Reutilizar um framework existente é útil quando poupa esse trabalho sem esconder as decisões que precisamos de controlar.
Nesta série, o create_agent do LangChain é um ponto de partida prático, porque fornece o loop de modelo/ferramentas e hooks para alterar o seu comportamento. Já corre sobre o LangGraph. Passar mais tarde para um workflow explícito em LangGraph significa assumir o controlo de mais etapas à volta desse agente; não significa acrescentar um grafo a um sistema que antes não tinha nenhum.
O primeiro design não precisa de conter todas as capacidades discutidas acima. Precisa do suficiente para concluir a sua tarefa dentro das suas restrições, mais evidência que revele onde falha. Antes de escrever a implementação, eu poria as decisões numa única página:
| Pergunta | Decisão a registar |
|---|---|
| Que tarefa estamos a concluir? | Quem chama, o resultado esperado, a recusa ou passagem a um humano válida e os efeitos proibidos |
| Quem escolhe o passo seguinte? | Etapas conhecidas no código; decisões delegadas no modelo; a razão de cada uma |
| Que verificações exigem interpretação? | A questão semântica, o seu contexto, o modelo candidato e o comportamento em caso de incerteza |
| O que pode o modelo pedir para fazer? | Nenhuma ferramenta, operações tipadas, código gerado ou uma combinação |
| Quem o autoriza e executa? | Recursos permitidos, serviço que impõe as regras, regras de aprovação e eventuais restrições da sandbox |
| O que tem de sobreviver a uma interrupção? | Estado da conversa, registos de negócio, operações pendentes e comportamento de recuperação |
| Que limites se aplicam? | Restrições do modelo/fornecedor, tratamento de dados, chamadas, tempo, gastos e cancelamento |
| O que prova a conclusão? | Verificações da resposta, das ações e do estado; quem é dono da sua evidência |
| O que deixámos de fora? | Componentes opcionais e a falha ou o novo requisito que justificaria cada um |
Uma resposta útil é suficientemente específica para mudar a implementação. “Precisa de memória” é vago. “Tem de retomar um reembolso aprovado depois de um worker reiniciar, sem criar um segundo reembolso” diz-nos o que persistir e que falha testar.
Esta é a Parte 1 de Building and Evaluating Agent Harnesses. A Parte 2 leva mais longe a questão do fluxo de controlo: quando é que um workflow explícito melhora o tratamento de uma tarefa o suficiente para justificar as suas etapas adicionais? Vamos construir o workflow mais pequeno que se justifique à volta do agente e compará-lo com o loop. Routers, workers paralelos e resumos são candidatos a investigar, não um destino predeterminado. As partes seguintes separam o avaliador do candidato e medem a variação entre execuções repetidas.
Opcional: inspecionar a implementação e os seus limites
A demo de acompanhamento é uma versão pequena e executável do assistente de suporte. Corre em dois ambientes:
- Tarefas de suporte personalizadas: doze tarefas, cada uma sobre uma base de dados SQLite nova de clientes e encomendas.
- τ³-bench retail: oito tarefas de um benchmark público em que um cliente simulado fala com o agente e verificações na base de dados pontuam o resultado.
Esta secção descreve o que a demo contém, o que deixa de fora de propósito, o que os resultados mostram e como executá-la. Pode saltá-la e usar na mesma o processo de design acima.
O que a demo contém
Um agente LangChain escolhe as chamadas. Tem cinco ferramentas de conta (look_up_account, list_orders, issue_refund, update_address e set_preference) e um servidor MCP para consultas de políticas e FAQ. Um checkpointer em memória guarda o estado de execução. Não há executor de código nem memória entre sessões.
A demo regista duas configurações:
| Spec | Componentes |
|---|---|
plain | Resumo, um limite de 20 chamadas ao modelo por execução e 2 novas tentativas em caso de erro de uma ferramenta |
base | Tudo o que está em plain, mais Schema-Guided Reasoning e um guard de escrita Jev |
O Schema-Guided Reasoning (SGR) faz o modelo devolver um objeto estruturado com o passo seguinte em vez de um tool call nativo. O guard Jev corre antes de cada escrita e faz uma única pergunta composta: esta chamada violaria a política ou iria além do que o cliente pediu? As duas perguntas separadas apresentadas antes neste artigo, sobre o pedido e a sua retirada, são uma proposta de redesenho. As execuções gravadas não as usaram.
base acrescenta SGR e Jev ao mesmo tempo, por isso os resultados personalizados não conseguem mostrar qual dos dois causou uma diferença.
O que a demo deixa de fora de propósito
- As ferramentas verificam a integridade dos dados, mas deixam a política de negócio ao agente, para que uma experiência possa expor uma escrita que a política proíbe. Um serviço real imporia a política no momento da escrita.
- Não há limite de tempo nem de gastos para a tarefa inteira, e as escritas repetidas não têm chave de idempotência.
- O avaliador corre no mesmo processo que o agente.
Por isso, estes resultados não testam a imposição de regras em produção, a segurança das novas tentativas nem a proteção da evidência contra adulteração.
Resultados nas tarefas personalizadas
Cada linha é uma execução por tarefa, com o modelo e os preços do endpoint registados com os resultados. Uma verificação de estado compara a base de dados final com a esperada. O custo e a latência são médias por tarefa; o custo inclui o Jev onde ele corre.
| Spec | Modelo | Verificações de estado | Custo | Latência |
|---|---|---|---|---|
base | openai/gpt-6-luna | 12/12 | $0.00073 | 11.9 s |
plain | openai/gpt-6-luna | 12/12 | $0.00033 | 5.8 s |
base | z-ai/glm-5.3-flash | 12/12 | $0.00101 | 9.7 s |
base | xiaomi/mimo-v2.6-flash | 7/12 | $0.00087 | 18.6 s |
- Com
gpt-6-luna, ambas as specs passaram as doze tarefas.basecustou cerca de 2,2 vezes mais e demorou cerca do dobro. - O Jev permitiu todas as escritas que verificou: doze nas execuções de
gpt-6-lunae GLM, e quatro na execução de MiMo. Nunca bloqueou uma escrita, por isso estas execuções não testam se ele trava uma escrita errada. - As cinco falhas do MiMo foram erros de formato de saída do SGR. Não há execução
plaindo MiMo, por isso não podemos dizer se o tool calling nativo se sairia melhor. - O resumo nunca correu. O maior pedido nas seis execuções gravadas (estas quatro e as duas do τ³-bench abaixo) teve 8351 tokens de input, abaixo do limiar de 12 000 tokens. Estes resultados não conseguem mostrar se o resumo ajuda.
Resultados no τ³-bench
O τ³-bench pontua uma execução reproduzindo os tool calls do agente num ambiente novo, por isso o benchmark tem de executar ele próprio as ferramentas. O adaptador pausa o grafo antes do seu nó de ferramentas, devolve os tool calls e escreve os resultados do benchmark antes de retomar.
Isto tem duas consequências. Primeiro, o prompt, as ferramentas e a política vêm do τ³-bench, por isso estes resultados não são comparáveis com os personalizados, mesmo com o mesmo ficheiro de spec. Segundo, a nova tentativa de ferramentas e o Jev nunca correm aqui: a única diferença entre base e plain é o SGR.
Ambas as execuções usam openai/gpt-6-luna para o agente e para o cliente simulado. A latência da simulação inclui o trabalho do simulador. O custo do agente e a latência da simulação são médias por tarefa; o custo do simulador é o total das oito tarefas.
| Spec | Passou | Custo do agente | Latência da simulação | Custo do simulador, total |
|---|---|---|---|---|
base | 6/8 | $0.00238 | 59.0 s | $0.00453 |
plain | 7/8 | $0.00136 | 38.9 s | $0.00471 |
- As oito tarefas vêm da divisão de teste de retail e não têm asserções em linguagem natural, por isso só as verificações na base de dados decidem a recompensa.
- Ambas as specs terminaram as oito tarefas sem erros do adaptador. Todas as falhas foram escritas em falta.
basefalhou as tarefas 9 e 26.plainpassou a tarefa 27 a um humano em vez de concluir a troca. - Uma segunda execução de
basetambém passou 6/8, mas falhou as tarefas 9 e 17. As tarefas que falham mudam entre execuções, por isso uma diferença de uma tarefa não é motivo para preferirplain. Estas execuções não medem essa variação.
Verifique os números registados
Os relatórios e traces guardados guardam os resultados por tarefa, as contagens de tokens, as configurações, as versões dos pacotes e os preços registados. Nas 393 chamadas de chat do agente nas seis execuções gravadas, o custo calculado a partir dos tokens coincidiu com o montante faturado em cada resposta. Esta verificação exclui as chamadas do Jev e do cliente simulado. Os preços são históricos, não uma cotação para a sua execução.
Execute-a por si
Instale o uv e use a versão marcada abaixo. Os testes correm offline depois de instaladas as dependências. As duas execuções nos ambientes fazem chamadas pagas a modelos através do OpenRouter e precisam de uma chave de API.
git clone https://github.com/slavadubrov/agent-harness-lab-public
cd agent-harness-lab-public
git checkout v0.1.2-a1
cp .env.example .env # set OPENROUTER_API_KEY
make test
make a1-custom SPEC=harness/spec/plain.yaml
make a1-tau3 SPEC=harness/spec/plain.yaml
Cada execução substitui os resultados em reports/article-a1/. Use git diff para os comparar com a baseline guardada no repositório. O Makefile instala a partir do uv.lock congelado.
Para experimentar outro modelo ou conjunto de ferramentas, copie uma spec em harness/spec/, altere esses campos e passe o seu caminho através de SPEC. As definições de fornecedor e de componentes personalizados aceitam chaves livres, por isso a validação não consegue apanhar todos os erros de digitação aninhados.
Pode usar o processo de design deste artigo sem executar a demo. A demo torna inspecionável um conjunto de escolhas; o contrato da sua própria tarefa decide quais delas pertencem ao seu harness.