DevSecOps

6 mai, 2026

Interfaces e Contratos: Como Agentes Respeitam Fronteiras

Publicidade

Duas equipes. Dois serviços. Um prazo apertado. Cada time trabalha no seu canto por duas semanas. No dia da integração, ligam os dois serviços e… nada funciona.

O serviço A manda o campo user_id como string. O serviço B espera um inteiro. O serviço A retorna erros com um campo error_message. O serviço B procura por error. O serviço A usa /api/v1/users/:id. O serviço B chama /users/get. E por aí vai.

Cada serviço funciona perfeitamente sozinho. Juntos, são incompatíveis.

Agora troca “duas equipes” por “dois prompts dados a um agente de IA” e o cenário é exatamente o mesmo.

O problema da interface implícita

Quando dois times não definem uma interface explícita antes de começar a codar, cada um inventa a sua. E como cada time tem contextos, experiências e preferências diferentes, as interfaces inventadas raramente são compatíveis.

Com humanos, isso é inconveniente mas recuperável. Os devs sentam juntos, olham os contratos, negociam, ajustam. Leva um dia, talvez dois. Frustrante, mas factível.

Com agentes de IA, o problema é mais profundo. O agente não negocia. Ele não olha o código do outro serviço pra ver como adaptar o seu. Quando recebe um prompt dizendo “crie um serviço de usuários”, ele gera a interface que acha mais provável com base no que aprendeu. E quando outro prompt pede “crie um serviço de pedidos que consome o serviço de usuários”, o segundo agente inventa a sua própria versão de como o serviço de usuários deveria funcionar.

O resultado é o mesmo das duas equipes: dois sistemas que funcionam isolados e quebram na integração.

Interface-first: a lição dos RFCs

Em engenharia de software, esse problema foi resolvido há décadas. A solução tem um nome simples: contract-first design – ou, no mundo dos RFCs, definição de interface antes da implementação.

A ideia é direta. Antes de qualquer time escrever uma linha de código, todos concordam com o contrato. O contrato define:

  • Endpoints: quais caminhos existem e o que cada um faz

  • Tipos de entrada: o que cada endpoint recebe, com tipos exatos

  • Tipos de saída: o que cada endpoint retorna, em caso de sucesso e de erro

  • Códigos de status: quais respostas HTTP (ou equivalentes) cada cenário produz

  • Formato de erros: como erros são representados

Quando o contrato existe antes do código, cada time pode implementar livremente por dentro. Pode mudar a arquitetura interna, trocar o banco de dados, refatorar tudo – desde que o contrato continue sendo respeitado. A interface é a fronteira. Dentro dela, liberdade total. Fora dela, responsabilidade compartilhada.

Esse princípio é tão fundamental que aparece em praticamente todo RFC bem escrito. A seção de “interfaces” ou “API specification” quase sempre vem antes da seção de implementação.

O mesmo princípio, aplicado a agentes

Quando você pede a um agente de IA para criar um serviço, o prompt é o seu RFC. E se o RFC não define interfaces, o agente improvisa.

Vamos ver isso na prática com um exemplo concreto.

Time A – prompt sem contrato:

Crie um serviço de usuários em Elixir/Phoenix que permita

criar, buscar e atualizar usuários. Use JSON para comunicação.

Time B – prompt sem contrato:

Crie um serviço de pedidos em Elixir/Phoenix que crie pedidos

para usuários existentes. Consulte o serviço de usuários

para validar que o usuário existe antes de criar o pedido.

O que acontece?

O agente do Time A pode retornar usuários assim:

{

"user": {

"id": "usr_abc123",

"full_name": "Maria Silva",

"email": "maria@example.com",

"created_at": "2026-03-12T10:00:00Z"

}

}

E o agente do Time B pode gerar código que espera isso:

{

"data": {

"user_id": 42,

"name": "Maria Silva",

"email": "maria@example.com"

}

}

O campo id virou user_id. O tipo mudou de string pra inteiro. O wrapper mudou de user pra data. O campo full_name virou name. Nenhuma dessas decisões está “errada” isoladamente – são escolhas válidas. Mas juntas, são incompatíveis.

O contrato como fundação do prompt

A solução é a mesma nos dois mundos: definir o contrato primeiro. Só que no mundo dos agentes, o contrato precisa estar dentro do prompt.

Veja como o mesmo cenário funciona quando você inclui o contrato:

Contrato compartilhado (incluído em ambos os prompts):

## Contrato da API de Usuários

### GET /api/v1/users/:id

Resposta de sucesso (200):

{

"id": integer,

"name": string,

"email": string

}

Resposta de erro (404):

{

"error": "not_found",

"message": string

}

Resposta de erro (422):

{

"error": "validation_failed",

"details": [{"field": string, "message": string}]

}

### POST /api/v1/users

Body esperado:

{

"name": string (obrigatório, max 100),

"email": string (obrigatório, formato email)

}

Resposta de sucesso (201): mesmo formato do GET

Time A – prompt com contrato:

Crie o serviço de usuários em Elixir/Phoenix.

Implemente os endpoints conforme o contrato abaixo.

NÃO altere os nomes de campos, tipos ou caminhos dos endpoints.

[contrato acima]

Time B – prompt com contrato:

Crie o serviço de pedidos em Elixir/Phoenix.

Ao validar que o usuário existe, chame o serviço de usuários

conforme o contrato abaixo. Use exatamente os campos e tipos

definidos no contrato para parsear a resposta.

[contrato acima]

Agora os dois agentes trabalham com a mesma fonte de verdade. O serviço A implementa a interface exatamente como definida. O serviço B consome a interface exatamente como definida. A integração funciona na primeira tentativa.

Type specs como contratos no Elixir

Se você trabalha com Elixir, já tem uma ferramenta poderosa pra definir contratos: typespecs. E elas funcionam muito bem dentro de prompts.

Em vez de descrever a interface em texto livre, você pode incluir specs que o agente vai respeitar:

@type user :: %{

id: integer(),

name: String.t(),

email: String.t()

}

@type error_response :: %{

error: String.t(),

message: String.t()

}

@spec get_user(integer()) :: {:ok, user()} | {:error, error_response()}

@spec create_user(map()) :: {:ok, user()} | {:error, error_response()}

Quando você inclui isso no prompt, está dando ao agente um contrato com a precisão de uma linguagem de programação, não a ambiguidade de uma descrição em prosa. O agente sabe exatamente que get_user recebe um inteiro e retorna uma tupla com um mapa de campos específicos.

É a diferença entre dizer “retorne os dados do usuário” e dizer “retorne {:ok, %{id: integer(), name: String.t(), email: String.t()}} ou {:error, %{error: String.t(), message: String.t()}}”. A segunda versão não deixa espaço pra interpretação.

O template contract-first pra prompts

Depois de aplicar esse padrão em vários projetos, cheguei num template que funciona consistentemente:

## Contexto

[O que o serviço faz e onde ele se encaixa no sistema]

## Contratos de Interface

### Interfaces que este serviço EXPÕE

[Endpoints, tipos de entrada, tipos de saída, códigos de erro]

### Interfaces que este serviço CONSOME

[Endpoints externos que ele chama, com formatos esperados de resposta]

## Regras de implementação

[Como o serviço deve funcionar internamente]

## Fora do escopo

[O que NÃO implementar]

A seção de contratos vem antes das regras de implementação. Isso é intencional. O contrato define a forma; a implementação preenche o conteúdo. O agente lê o contrato primeiro e já sabe quais são as fronteiras antes de começar a escrever código.

Por que contratos explícitos importam mais pra agentes do que pra humanos

Um dev humano, quando encontra uma inconsistência na integração, faz o que qualquer profissional faz: investiga, pergunta, adapta. Ele abre o código do outro serviço, lê a documentação, manda uma mensagem no Slack. É um processo lento, mas funciona.

O agente não faz nada disso. Ele trabalha com o que tem no prompt. Se o prompt não tem o contrato, ele inventa. E o que ele inventa é baseado em padrões estatísticos de milhões de repositórios – ou seja, vai ser algo razoável, mas não vai ser o seu contrato.

Isso significa que, paradoxalmente, contratos explícitos importam mais pra agentes do que pra humanos. Humanos compensam a falta de contrato com comunicação informal. Agentes não têm essa opção.

O efeito cascata

Uma interface mal definida num prompt não quebra só um serviço. Ela quebra tudo que depende dele.

Se o agente gera um serviço de usuários com campos diferentes do esperado, o serviço de pedidos quebra. Se o serviço de pedidos está quebrado, o serviço de pagamentos que depende dele também. E se você está usando agentes pra gerar vários serviços ao mesmo tempo, cada interface implícita é uma bomba-relógio.

A abordagem contract-first evita o efeito cascata na raiz. Quando todos os contratos estão definidos antes da implementação, cada agente pode trabalhar de forma independente sem risco de incompatibilidade. É o mesmo princípio que permite que times distribuídos trabalhem em paralelo – o contrato é o ponto de sincronização.