# Truncamento de resultados da API: impeça agentes de fazer alterações incorretas

Um agente não precisa de uma ferramenta maliciosa para causar uma alteração prejudicial. Basta uma ferramenta que retorne silenciosamente apenas parte da resposta. Dê ao agente um resultado de busca que pareça completo e peça que ele remova o que a busca não encontrou. Muitas vezes, ele produzirá um erro perfeitamente lógico a partir de premissas falsas.

A solução não é escrever um prompt de sistema mais longo dizendo ao agente para ter cuidado. A ferramenta precisa informar, em um formato estável e legível por máquina, se concluiu o trabalho solicitado, o que foi omitido, por que isso aconteceu e como o chamador pode continuar. Se a ferramenta não consegue fazer essa declaração, o agente não deve usar a ausência no resultado como permissão para uma alteração ampla.

## Dados parciais e dados vazios são afirmações diferentes

Um resultado vazio diz que a ferramenta não encontrou itens correspondentes dentro do escopo que realmente examinou. Um resultado vazio completo diz que a ferramenta examinou todo o escopo solicitado e não encontrou nada. São afirmações diferentes, mas a maioria dos contratos de API as reduz ao mesmo `[]`.

Essa redução leva a uma inferência errada específica:

1. O agente pede todas as contas de serviço sem um responsável atual.
2. A API retorna um array vazio depois de examinar a primeira página, parar ao atingir um limite de resultados ou omitir registros que o token não pode ler.
3. O agente conclui que todas as contas têm um responsável.
4. Ele altera um controle, relatório ou trabalho de limpeza relacionado com base nessa conclusão.

O agente não precisou interpretar o inglês de forma errada. A ferramenta deu uma resposta cujo formato sugeria mais do que o servidor realmente sabia.

Uma ferramenta deve distinguir pelo menos quatro estados. Uma consulta completa pode retornar itens. Uma consulta completa pode não retornar itens. Uma consulta incompleta pode retornar alguns itens. Uma consulta incompleta pode não retornar nenhum item. Dar atenção apenas ao terceiro estado deixa passar o caso mais perigoso: uma resposta vazia que convence o agente de que um problema não existe.

As permissões tornam isso ainda pior. Muitos serviços escondem deliberadamente objetos inacessíveis retornando uma coleção vazia ou filtrada, em vez de um erro de permissão. Esse comportamento pode ser razoável em uma interface voltada para pessoas. Ele é uma evidência inaceitável para uma tarefa autônoma de limpeza, a menos que a API informe exatamente qual era a visibilidade do chamador.

Não use frases como `Alguns resultados podem estar ausentes` como contrato. Isso não dá ao agente um caminho confiável a seguir nem oferece ao engenheiro uma condição testável. Um campo chamado `complete` com um valor booleano parece sem graça. Essa é justamente a ideia.

## Uma resposta HTTP bem-sucedida ainda pode estar incompleta

Os códigos de status HTTP descrevem a troca entre cliente e servidor. Sozinhos, eles não comprovam que uma busca, inventário ou exportação cobriu o domínio solicitado.

A RFC 9110 define a semântica dos códigos de status HTTP. Um `200 OK` diz que a solicitação foi bem-sucedida de acordo com a semântica do método. Ele não diz que uma busca cobriu todas as páginas, partições, domínios de permissão ou registros antes do prazo. As equipes costumam interpretar `200` como algo maior do que o protocolo promete.

Considere esta resposta:

```json
HTTP/1.1 200 OK
Content-Type: application/json

{
  "items": [],
  "next_cursor": null
}
```

Ela parece definitiva. Mas `next_cursor: null` só diz que esse mecanismo específico de paginação não tem uma próxima página. Não diz nada sobre um limite de resultados no backend, um trabalho de busca expirado, uma fonte que falhou, registros excluídos por política ou uma API que limita silenciosamente o período consultado.

Uma resposta se torna uma evidência utilizável quando o contrato informa o que `complete` cobre. Para uma busca de contas, isso pode significar todas as contas visíveis para a identidade que fez a chamada em um snapshot especificado. Para uma busca de código, pode significar todos os arquivos indexados em uma revisão especificada, excluindo explicitamente arquivos ignorados e arquivos gerados que não foram indexados. O escopo precisa ser concreto o bastante para que o chamador decida se ele corresponde à ação proposta.

Não resolva isso retornando `500` para toda resposta parcial. Resultados parciais podem ser úteis. Um painel pode mostrá-los. Um agente pode resumi-los. Uma pessoa pode inspecioná-los. O erro está em apresentar um resultado parcial como resposta autorizada para uma pergunta que exige completude.

Use um erro quando a operação solicitada promete uma resposta atômica ou completa e não consegue cumprir essa promessa. Use uma resposta bem-sucedida com incompletude explícita quando os dados parciais ainda tiverem um uso legítimo. O cliente precisa de uma distinção determinística, não de uma discussão sobre se `200` pareceu otimista.

## Coloque metadados de completude ao lado de todo resultado

Um contrato de resultados deve expor a completude como dados estruturados, tanto quando a lista de itens está completa quanto quando é curta ou vazia. Não faça os chamadores inferirem isso pela quantidade de itens, pela ausência de um cabeçalho ou por uma frase em um campo `message`.

Este formato funciona para uma busca de coleção:

```json
{
  "items": [
    {"id": "svc-184", "owner": null}
  ],
  "complete": false,
  "truncated": true,
  "incomplete_reasons": [
    {
      "code": "RESULT_LIMIT_REACHED",
      "message": "The query stopped after the configured result limit.",
      "limit": 1000
    }
  ],
  "next_cursor": "eyJvZmZzZXQiOjEwMDB9",
  "scope": {
    "resource": "service_accounts",
    "visibility": "resources readable by this credential",
    "snapshot": "2025-03-08T14:20:11Z"
  },
  "warnings": []
}
```

Os nomes exatos dos campos importam menos do que seu significado e sua consistência. `complete` é o campo que orienta a decisão. `truncated` descreve uma causa importante de incompletude, mas não deve virar um termo genérico. Um filtro de permissão não é truncamento. Uma busca federada que atingiu o tempo limite não é paginação. Se você sobrecarregar um único sinalizador, os chamadores perderão o motivo de que precisam para se recuperar com segurança.

Mantenha `warnings` separado de `incomplete_reasons`. Um aviso pode informar que um campo obsoleto apareceu, que um valor foi normalizado ou que a ordenação solicitada voltou ao padrão. Um motivo de incompletude diz que a resposta não pode sustentar afirmações sobre a parte não retornada do escopo solicitado. Essa distinção determina se um agente pode prosseguir.

Evite também um sinalizador isolado `has_more` como único indicador. Em geral, ele responde a uma pergunta estreita sobre paginação. Um agente que vê `has_more: false` pode concluir de forma razoável que a coleção terminou, mesmo quando um limite no servidor ou uma partição inacessível impediu uma varredura completa. `has_more` pode continuar existindo, mas não deve carregar sozinho todo o significado de completude.

Para a leitura de um recurso individual, aplique a mesma disciplina. Uma resposta com campos omitidos deve dizer se o servidor os omitiu porque o chamador não os solicitou, não tem acesso a eles, a fonte de dados falhou ou o valor realmente não existe. A omissão em JSON é compacta, mas ambígua.

## A paginação precisa de um limite estável, não de uma página maior

A paginação só é segura para agentes quando a API torna a continuação confiável e explica quais alterações podem invalidá-la. Aumentar o limite da página apenas adia o problema. Não o elimina.

A paginação por deslocamento é especialmente propensa a conclusões erradas. Um agente lê os registros de 0 a 99, exclui ou cria um objeto e depois lê os registros de 100 a 199. Se a ordenação subjacente mudou, ele pode pular um registro ou processar outro duas vezes. Para um relatório informativo, isso pode ser aceitável. Para um plano de alteração, pode ser desastroso.

A paginação por cursor costuma ser melhor porque o servidor pode codificar uma posição em um conjunto de resultados ordenado. Ainda assim, ela precisa de um contrato. Informe se o cursor congela um snapshot, por quanto tempo ele continua válido e se a alteração dos filtros, da ordenação ou da autorização o invalida. Se um cursor expirar, não reinicie a varredura silenciosamente e retorne uma resposta combinada. Retorne um estado de incompletude explícito ou obrigue o cliente a começar de novo.

Uma resposta de coleção útil dá ao chamador informações suficientes para terminar de forma deliberada:

```json
{
  "items": ["item-001", "item-002"],
  "complete": false,
  "next_cursor": "cD0y",
  "page": {
    "returned": 2,
    "requested_size": 2,
    "ordering": "id ascending",
    "snapshot": "search-7f9c"
  },
  "incomplete_reasons": [
    {"code": "MORE_PAGES_AVAILABLE"}
  ]
}
```

O chamador deve continuar até receber `complete: true`, e não apenas até receber uma página curta. Páginas curtas acontecem por vários motivos. Algumas APIs as retornam porque uma partição está temporariamente esparsa, porque um worker interno parou cedo ou porque o serviço limita o tamanho da resposta em bytes, e não pela quantidade de objetos.

Não peça ao modelo de linguagem para memorizar esse loop em prosa. Coloque o comportamento de paginação na implementação da ferramenta. Uma ferramenta de alto nível como `search_all` pode coletar páginas, preservar o snapshot, limitar seu próprio trabalho e informar se chegou a um estado final. Se atingir seu próprio limite, deve retornar `complete: false` e dizer que o limite do cliente causou a interrupção.

Esse último caso é frequentemente esquecido. Os engenheiros adicionam corretamente metadados à API e depois criam um wrapper de agente com `max_pages=10`, descartando o fato de que ele parou na décima página. O wrapper virou a fonte da incompletude. O contrato da ferramenta mais externa é responsável por divulgá-la.

## Limites de tempo, partições com falha e permissões precisam de motivos próprios

Uma busca pode concluir sua solicitação HTTP enquanto partes do trabalho ainda não terminaram. Serviços distribuídos costumam encaminhar uma consulta para vários índices ou locatários. Se uma fonte atinge o tempo limite e o serviço retorna correspondências das outras, o resultado pode ser útil, mas está incompleto.

Represente a causa com um código que o programa possa usar para decidir o próximo passo. O texto para pessoas deve vir ao lado dele, e não substituí-lo. Mantenha os códigos poucos, estáveis e documentados. Por exemplo:

- `MORE_PAGES_AVAILABLE` significa que o chamador pode solicitar a próxima página.
- `RESULT_LIMIT_REACHED` significa que o serviço aplicou um limite antes de esgotar as correspondências.
- `TIME_BUDGET_EXCEEDED` significa que a busca parou antes de concluir todo o trabalho planejado.
- `SOURCE_UNAVAILABLE` significa que uma fonte identificada não respondeu.
- `VISIBILITY_RESTRICTED` significa que a autorização do chamador excluiu parte do domínio solicitado.

Não esconda `VISIBILITY_RESTRICTED` por trás de uma resposta genérica de sucesso. As equipes de segurança às vezes preferem respostas indistinguíveis para não revelar qual objeto existe. Essa preocupação é legítima. A API pode informar que os limites de visibilidade impedem um inventário completo sem identificar objetos ocultos. Ela não pode permitir que o chamador confunda um inventário parcial com um inventário exaustivo.

A mesma regra se aplica a limites de taxa e cotas. Se uma API lê a primeira parte de uma solicitação antes de esgotar um orçamento, informe os dados retornados e a condição orçamentária. Uma nova tentativa pode terminar o trabalho mais tarde, mas é uma nova tentativa. O agente não deve combinar duas tentativas em uma afirmação de completude, a menos que a API forneça um snapshot estável ou a tarefa aceite alterações durante o processo.

Um prazo deve ser uma entrada e uma saída. Quando um agente pede um inventário amplo, permita que ele defina um orçamento de tempo e receba a quantidade de trabalho concluída. Assim, o compromisso fica visível. Uma busca de reconhecimento de dez segundos pode bastar antes de uma revisão humana. Ela é uma evidência fraca para excluir todos os recursos que a busca não encontrou.

## A ausência é uma evidência fraca para alterações destrutivas

Um agente pode usar dados parciais com segurança para preparar um relatório, identificar candidatos ou pedir que uma pessoa inspecione um alvo pequeno. Ele não deve usar dados parciais para concluir que um recurso não é usado, não tem responsável, é duplicado ou pode ser removido.

A diferença está na direção da afirmação. Encontrar um registro com `owner: null` é uma evidência positiva sobre esse registro, sujeita à atualização do campo. Não encontrar registros sem responsável é uma afirmação universal sobre o domínio da busca. Afirmações universais exigem cobertura completa de um escopo definido.

Essa falha costuma aparecer disfarçada de melhoria de eficiência. Uma equipe dá a um agente uma ferramenta chamada `list_inactive_projects` e permite que ele arquive todos os projetos retornados ou, pior ainda, todos os projetos ausentes de uma segunda lista. A ferramenta tem um limite máximo de resultados. Meses depois, uma organização grande ultrapassa esse limite. Ninguém alterou o prompt do agente, mas o significado mudou de «agir sobre o inventário» para «agir sobre um prefixo arbitrário do inventário».

Crie ferramentas de ação que exijam evidências, em vez de aceitarem uma narrativa. Uma operação de arquivamento pode exigir os IDs selecionados por um inventário completo anterior e um token de snapshot que vincule a seleção à leitura. Se o inventário estiver incompleto, a ferramenta rejeita a operação. Assim, a verificação de segurança fica em um lugar no qual o modelo não pode contorná-la com explicações vagas.

Para ações que não podem usar tokens de snapshot, exija um escopo explícito e verifique novamente cada alvo no momento da execução. Isso não prova que a busca original foi exaustiva, mas impede que uma lista antiga autorize alterações não relacionadas. Mantenha a ação restrita o bastante para que um revisor entenda o conjunto de alvos.

A alternativa popular é dizer ao agente: «Nunca exclua nada a menos que tenha certeza». Parece sensato, mas falha na prática. Certeza é uma palavra em um prompt. `complete: false` é uma condição que uma ferramenta pode impor.

## Os esquemas das ferramentas devem obrigar o agente a encarar a incerteza

Uma ferramenta MCP ou qualquer wrapper voltado para agentes deve retornar um envelope tipado, não um bloco atraente de prosa. O modelo consegue ler prosa, mas os sistemas ao redor precisam de campos que possam validar, registrar, bloquear e testar.

Um tipo de resposta prático poderia ser assim:

```json
{
  "status": "partial",
  "data": {
    "repositories": [
      {"id": "repo-a", "default_branch": "main"}
    ]
  },
  "completeness": {
    "complete": false,
    "reasons": ["TIME_BUDGET_EXCEEDED"],
    "continuation": {
      "kind": "retry_with_deadline",
      "minimum_seconds": 30
    }
  },
  "warnings": [
    {
      "code": "STALE_INDEX",
      "message": "Search index may lag the source repository."
    }
  ]
}
```

Não use `status: "success"` para essa resposta. Isso incentiva clientes simples a descartar os metadados. `partial` informa ao chamador que ele recebeu dados úteis com uma restrição. Se o protocolo precisar usar um único status de sucesso, torne `complete` obrigatório e exija que os clientes capazes de executar ações o examinem antes de qualquer alteração.

O campo de continuação deve descrever uma rota real de recuperação. `next_cursor` é apropriado para outra página. `retry_after` se aplica a um limite de taxa. `narrow_query` pode ser adequado a um limite do servidor. Não ofereça uma continuação que apenas repete a mesma consulta esperando que o universo se comporte de outra forma.

As instruções do agente devem estabelecer um conjunto pequeno e rigoroso de regras:

- O agente pode usar uma coleção vazia como prova de ausência somente quando `complete` for verdadeiro.
- O agente pode usar uma resposta parcial para propor uma investigação de leitura, limitada e sem alterações.
- O agente deve expor `incomplete_reasons` antes de pedir aprovação para qualquer ação que dependa do resultado.
- O agente não deve inventar um token de continuação ausente nem afirmar que uma nova tentativa funcionou sem o resultado correspondente.

Essas regras são curtas porque os dados carregam os detalhes. Um prompt não consegue recuperar informações que a ferramenta decidiu não relatar.

## Os avisos precisam de responsabilidade e de um caminho de expiração

Os avisos viram parte do cenário quando toda resposta emite uma cautela vaga. Mantenha-os específicos, atribuíveis e acionáveis. Um aviso que nunca muda a próxima decisão do chamador normalmente deveria virar documentação ou desaparecer.

Por exemplo, `STALE_INDEX` deve identificar a fonte indexada e, quando possível, sua revisão observada ou o horário da atualização. Assim, o agente pode decidir inspecionar a fonte oficial antes de alterar o código. `PARTIAL_FIELD_SET` deve informar quais campos o servidor omitiu e se o chamador pode solicitá-los. `DEFAULT_SCOPE_APPLIED` deve declarar o escopo escolhido pelo servidor, porque os padrões são uma fonte frequente de ações amplas acidentais.

Não transforme avisos em bloqueios por acidente. O chamador precisa de uma regra clara de gravidade. Os metadados de completude determinam se o resultado sustenta uma afirmação sobre todo o escopo. Os avisos tratam de confiança, atualidade ou interpretação. Uma ferramenta pode retornar `complete: true` com um aviso de desatualização. Esse resultado ainda pode enumerar todos os itens de um índice e, ao mesmo tempo, ser inadequado para uma alteração que exige o estado atual.

Dê aos avisos códigos estáveis e teste os consumidores com base neles. Evite testes que verifiquem apenas uma mensagem amigável. As mensagens mudam quando um redator melhora a linguagem; a regra de decisão não deve mudar.

Decida também quem será responsável por um aviso depois que ele for lançado. Se uma equipe de operações vê o mesmo aviso em todas as chamadas durante seis meses, deixará de lê-lo. Repare a condição subjacente, transforme o aviso em uma falha grave quando for apropriado ou remova-o se ele não afetar nenhuma decisão. Luzes amarelas permanentes ensinam pessoas e agentes a ignorar luzes amarelas.

## Os testes precisam exercitar a resposta vazia perigosa

A maioria dos conjuntos de testes cobre uma página normal de resultados e um erro do servidor. Eles ignoram a resposta que causa a pior inferência: `items: []` junto com incompletude.

Escreva testes de contrato para cada código de motivo. Verifique se a API retorna os metadados para listas preenchidas e vazias, se os SDKs os preservam e se o wrapper do agente não os reduz a texto. Uma regressão em qualquer camada pode transformar uma resposta honesta do servidor em um resultado enganoso da ferramenta.

Use casos como estes em um fixture de teste:

```json
{
  "case": "empty first page with more pages",
  "response": {
    "items": [],
    "complete": false,
    "truncated": false,
    "incomplete_reasons": ["MORE_PAGES_AVAILABLE"],
    "next_cursor": "cursor-2"
  },
  "expected_agent_decision": "continue_search"
}
```

Depois, teste uma solicitação de alteração após esse fixture. A decisão esperada deve ser `refuse_or_request_review`, e não `perform_cleanup`. Deixe a política visível no nome do teste. Caso contrário, futuros responsáveis tratarão a proteção como um caso extremo cauteloso demais e a removerão para tornar uma demonstração de automação mais fluida.

Teste também a paginação durante alterações. Insira, exclua e reordene registros entre páginas. Faça um cursor expirar. Faça uma partição falhar depois que outra tiver retornado resultados. Remova uma permissão no meio da varredura. Sua ferramenta deve preservar um snapshot documentado ou informar que não pode afirmar completude. Um teste que usa apenas um banco de dados falso e estático não detecta as mentiras que aparecem em produção.

Testes de propriedades ajudam nesse ponto. Gere coleções maiores que todos os limites configurados, varie os tamanhos de página e verifique um invariante: um cliente só pode marcar uma coleção como completa depois de contabilizar cada item do snapshot declarado. O teste não precisa de um modelo de linguagem. Isso é apenas correção de interface.

## A aprovação humana deve revelar as evidências ausentes

O controle humano só funciona quando a aprovação mostra a decisão que está sendo apresentada a uma pessoa. «Permitir ação do agente» não é uma aprovação. É um pedido para aceitar uma cadeia opaca de suposições.

Quando uma ferramenta informa dados incompletos, mostre a ação proposta, o escopo do alvo, o motivo pelo qual as evidências estão incompletas e a opção de recuperação. Um pedido útil diz que o inventário atingiu o tempo limite depois de retornar 842 recursos e pergunta se a pessoa quer tentar novamente com um prazo maior, restringir a ação aos IDs retornados ou abandonar a alteração. Assim, o revisor pode fazer uma escolha real.

O Sallyport mantém a credencial fora do processo do agente quando executa ações HTTP e SSH, e seus registros de atividade podem mostrar as chamadas resultantes. Esse isolamento e esse rastreamento são úteis quando um revisor precisa reconstruir uma decisão equivocada. Eles não transformam uma resposta ambígua da API em evidência, por isso a resposta da ferramenta ainda precisa carregar seu estado de completude.

Evite a fadiga de aprovações reservando a aprovação para ambiguidades relevantes. Uma ferramenta deve cuidar da continuação rotineira, como buscar uma próxima página documentada, sem interromper repetidamente uma pessoa. Ela deve parar ao chegar a um limite de política: um snapshot expirado, visibilidade restrita, uma ação baseada em ausência ou uma alteração proposta fora das evidências coletadas.

A primeira tarefa de engenharia é pequena: encontre todos os wrappers de API que podem retornar uma lista, um agregado ou um resultado de busca e adicione um estado explícito de completude à resposta mais externa. Comece por resultados vazios e buscas com limite. É nesses casos que agentes confiantes fabricam as respostas erradas mais convincentes.
