Alguém fez ao assistente uma pergunta razoável:
Me dê um relatório das minhas tarefas desta semana, separado em Em andamento e Concluído.
Ele respondeu certo. Também levou uns quarenta segundos e mais de trinta chamadas de ferramentas para isso, porque listou as tarefas e depois abriu o histórico de cada uma, uma por vez, para descobrir quando a tarefa tinha sido movida e por quem.
Esse é o problema N+1. Todo engenheiro de backend já o encontrou num ORM: você carrega uma lista e depois dispara uma consulta por linha, porque o que você realmente precisava não estava na lista. Aqui a forma é a mesma, com um motor de consultas mais lento e mais caro (um modelo de linguagem) no lugar onde antes ficava o loop.
O agente não estava sendo burro. A API do BugDump não deu a ele outro jeito de responder.
O agente só pode perguntar o que você expôs
Um modelo com ferramentas faz algo parecido com planejamento de consultas. Ele lê as descrições das suas ferramentas, descobre quais chamadas podem produzir a resposta e as executa. Se o eixo de que ele precisa não é um filtro, ele reconstrói esse eixo à mão: busca linhas e examina cada uma.
O list_tasks do BugDump filtrava por responsável, por status, por coluna, por etiqueta, por qualquer propriedade personalizada e por um intervalo de datas de criação. O que ele não conseguia era responder quando isto foi concluído. A data de conclusão existia, numa coluna chamada resolvedAt, indexada e pronta. Só não era um filtro, e não vinha na resposta.
Então «o que eu concluí esta semana», do ponto de vista do modelo, se decompôs em: listar as tarefas fechadas, depois abrir o histórico de cada uma e procurar a mudança de coluna. Uma chamada por tarefa. A informação estava a uma cláusula WHERE de distância.
Três perguntas que valem para toda ferramenta
Desde então, auditei as ferramentas com três perguntas. Vale a pena roubá-las.
1. Quem chama consegue filtrar por todos os eixos que os dados têm?
Não «todos os campos», e sim todos os eixos sobre os quais alguém vai perguntar. O tempo quase sempre é um deles, e quase sempre é mais de um: criado, atualizado, concluído, mexido por último. Cada um é uma pergunta diferente. Uma ferramenta que expõe só createdAt obriga quem pergunta sobre os outros três a paginar pelas linhas.
A correção foram dois parâmetros, resolvedFrom e resolvedTo. Quatro linhas de código no servidor. Isso eliminou uma classe inteira de N+1 de todo relatório que um agente escreve.
2. A ferramenta sabe quem está chamando?
«Minhas tarefas» é a expressão mais comum nesses pedidos, e por muito tempo o agente não conseguia responder a isso sem chutar. get_current_project retornava os membros do projeto (nomes, funções, ids), mas nunca dizia em nome de qual deles este token age.
Veja o que um agente faz com isso. Ele olha a lista de membros, vê «Alex Rivera», lembra que o usuário disse «minhas» e escolhe aquele cujo nome parece mais próximo de algo que viu antes. Funciona até uma equipe ter duas pessoas chamadas Alex.
Agora o servidor retorna um objeto viewer: o id de usuário, o nome e a função do membro a quem o token pertence. Identidade não deveria ser uma dedução.
3. A lista retorna o que o detalhe retorna?
Se get_task inclui um campo e list_tasks o omite, você construiu um loop de buscas. O agente recebe vinte linhas, precisa daquele campo para as vinte e chama o endpoint de detalhe vinte vezes.
Os itens da lista do BugDump não traziam resolvedAt nem updatedAt, enquanto o detalhe trazia os dois. Acrescentá-los é uma mudança de duas linhas que elimina vinte idas e voltas.
A armadilha: um timestamp que mente
Enquanto adicionava os filtros, quase lancei um quarto (updatedFrom / updatedTo sobre updatedAt), porque é o jeito óbvio de perguntar «o que mudou esta semana».
Ele teria sido ativamente prejudicial, e o motivo vale em geral.
O updatedAt da tabela de tarefas do BugDump é mantido pelo ORM: qualquer UPDATE na linha o atualiza. Mas as tarefas guardam status, responsável, prioridade, etiquetas e data de entrega numa tabela de valores separada. Então:
atribuir uma tarefa não altera
updatedAt;mudar a prioridade dela não o altera;
movê-la entre duas colunas abertas não o altera;
um estranho votando nela no seu portal público altera.
Um filtro baseado nessa coluna teria respondido «em que eu trabalhei esta semana» com uma lista que excluía o trabalho e incluía o clique de um estranho. Pior: teria parecido plausível. O modo de falha de uma ferramenta ruim não é um erro, é uma resposta errada dada com confiança.
Deixei esse filtro de fora e disse isso na descrição da ferramenta: updatedAt marca quando a linha foi tocada, não quando houve atividade. A descrição de uma ferramenta é uma documentação que o modelo de fato lê. Use-a para descartar coisas, não só para explicá-las.
Quanto custa errar nisso
A versão de um endpoint lento vista por um agente não é uma página lenta. É:
Latência que o usuário sente. Trinta idas e voltas em sequência, cada uma esperando o modelo decidir a próxima.
Janela de contexto queimada. Cada resposta são tokens. Trinta históricos de tarefas podem espremer para fora aquilo que o usuário realmente pediu.
Dinheiro. São tokens cobrados, toda vez que alguém pergunta.
Truncamento silencioso. Um agente que fica sem espaço interrompe o trabalho antes da hora e resume o que tem, e a resposta dele não diz «só li onze das quarenta tarefas».
Esse último ponto é o motivo de isto ser um problema de correção, e não de desempenho.
O mesmo relatório, agora
Com as três correções no lugar, o relatório semanal fica assim:
get_current_project— quem sou eu e quais são as colunas;list_taskscomassigneeId+ a coluna Em andamento;list_taskscomassigneeId+status: "closed"+resolvedFrom.
Três chamadas, fixas, não importa quantas tarefas voltem. Cada linha já traz resolvedAt, então agrupar por dia não exige mais nada.
Uma ressalva que documento em vez de esconder: resolvedAt registra quando uma tarefa entrou pela última vez numa coluna fechada. Reclassifique uma coluna de aberta para fechada, e todas as tarefas nela recebem aquele momento como data, então o relatório de uma semana pode incluir trabalho que na verdade foi concluído meses antes. Agentes não conseguem deduzir ressalvas assim. Escreva-as onde o modelo vai lê-las.
Se você está construindo um servidor MCP
Faça uma pergunta real a um agente real e observe o log de chamadas. Não o caminho feliz que você desenhou, e sim a pergunta que um usuário realmente digitaria, com as palavras dele, numa sexta-feira à tarde.
Cada loop que você vê é um eixo que a sua API não expôs. A correção costuma ser uma cláusula WHERE, um campo na resposta de uma lista ou uma frase numa descrição. Sai muito mais barato do que o contorno que o modelo inventa.
O servidor MCP do BugDump é gratuito em todos os planos, inclusive no Grátis, e Usando o BugDump com o Claude Code (MCP) mostra como conectá-lo ao seu projeto.
Leia também
O que aprendi criando um servidor MCP para uma ferramenta de desenvolvedores — mais decisões de design para que um agente use as ferramentas certo já na primeira tentativa.
Depure relatórios de bug dos usuários com o Claude Code: o servidor MCP do BugDump — como um assistente de IA lê o console, a rede e o replay de um relatório e encontra o bug no código.
OAuth no servidor MCP: Claude e ChatGPT se conectam sem token — como o servidor MCP faz login de apps que não têm onde colar um token.