Guia prático · Python

Como construir um AI harness em Python (passo a passo)

Por Bruno Americo · 19 de julho de 2026 · atualizado em 15 de outubro de 2026

Você não projeta um AI harness de uma vez, você cresce ele a partir de falhas observadas. A Anthropic recomenda começar simples e só adicionar complexidade quando o simples não basta. Mitchell Hashimoto dá o motor: toda vez que o agente erra, você engenheira uma correção permanente. Depois você sistematiza em guides e sensors.

Nota: "AI harness" aqui é o conceito de software — a infraestrutura em volta de um LLM que o torna um agente confiável —, não a harness.io (plataforma de CI/CD) nem o chicote/arreio literal da palavra em inglês.

Comece simples, adicione complexidade só quando precisar

O primeiro passo não é montar um agente multi-etapas. É o menor prompt que resolve. A Anthropic, em "Building Effective Agents" (dezembro de 2024), é direta: comece com prompts simples e só adicione sistemas agênticos de múltiplos passos quando as soluções mais simples não bastarem. A razão é prática. Você não sabe onde o agente vai falhar até rodar, e cada peça de complexidade que você adiciona antes de ver uma falha real é uma aposta. Construa o mínimo, rode, e deixe as falhas te dizerem o que engenheirar.

O loop de erro para regra é o motor

É aqui que o harness cresce. Hashimoto descreve o loop assim: toda vez que você acha um erro do agente, você para e engenheira uma solução pra que o agente nunca mais cometa aquele erro ("My AI Adoption Journey"). Não é corrigir na mão e seguir. É transformar a falha em regra permanente. Na prática, cada linha do seu AGENTS.md ou CLAUDE.md nasce de um comportamento ruim que você viu. O arquivo de regras não é escrito de uma vez, ele é o registro acumulado de tudo que já deu errado.

Os cinco passos, em ordem

PassoO que fazerExemplo concreto
1. Começar simplesO menor prompt que resolve; complexidade só quando o simples falhaUm prompt único antes de montar um agente multi-etapas
2. Loop erro para regraCada falha vira correção permanente, não ajuste na horaViu o agente errar, escreve a regra que impede o erro
3. GuidesDirecionam o agente antes de ele agir (feedforward)CLAUDE.md, regras de operação, ferramentas permitidas
4. SensorsDão feedback depois da ação pra ele se auto-corrigirTestes, linters e typecheck que o agente lê
5. Memória e observabilidadeRegras persistem entre sessões e o comportamento fica auditávelRegras versionadas, artefatos por fase (spec, plano, relatório)

Sistematize em guides e sensors

Depois de alguns ciclos, os padrões aparecem, e é aí que você sistematiza. Birgitta Böckeler (Thoughtworks) dá os dois primitivos de construção (martinfowler.com, abril de 2026): guides, que direcionam o agente antes de ele agir (regras, ferramentas permitidas), e sensors, que dão feedback depois (testes, linters, typecheck que o agente lê). Hashimoto reforça o lado sensor: dê ao agente ferramentas rápidas e de qualidade que digam automaticamente quando ele está errado. A Atlan propõe uma etapa a mais, uma "camada de dados", como terceiro passo da sequência (guides, sensors, camada de dados). Vale registrar que a Atlan é uma empresa de catálogo de dados, então esse passo é interessado, não consenso neutro. Guides e sensors são o núcleo; o resto depende do seu contexto.

O mesmo método, num arquivo Python

Até aqui o método está em palavras: comece simples, transforme cada erro em regra, separe guide de sensor. O arquivo abaixo é esse método em código que roda sem chave de API. O método model é um PLACEHOLDER. Ele não chama OpenAI, Anthropic nem qualquer outro provedor. Ele devolve uma ação fixa para o loop poder ser executado offline. Troque esse método quando você tiver um cliente de verdade. Não publique a saída dele como nota.

O exemplo inteiro cabe num arquivo. Python 3.11 ou mais novo, por causa de list[str] e str | None. Não há dependência externa. Rode com python harness.py. O que você deve ver está mais abaixo, copiado de uma execução deste arquivo, não de um modelo.

Como ler o arquivo

Turn é uma linha de diário: a tarefa, a ação escolhida e o erro, se houve. Harness guarda três coisas. rules é a lista de regras permanentes, o guide que cresce. denied é o conjunto de ações que o guardrail barra antes de qualquer efeito. log é o sensor em forma de lista: cada tentativa fica registrada, inclusive a que falhou.

model olha o texto da tarefa e as regras já escritas. Se a tarefa fala em refund e ainda não existe regra sobre order id, devolve refund_without_id. Se a regra já entrou na lista, devolve ask_order_id. Se a tarefa pede para apagar a base de produção, devolve delete_production. Qualquer outra tarefa devolve answer. Isso é um script de teatro, escrito para o tutorial ter um caminho previsível. Um modelo de linguagem não se comporta assim. O comentário no método diz isso de novo, de propósito.

guardrail roda antes de a ação valer. Se o nome da ação está em denied, a função devolve a string guardrail blocked mais o nome. delete_production e send_email estão no conjunto. observe roda depois, e só conhece um erro de domínio: refund sem order id. rule_for transforma esse erro numa frase. A frase é a regra. Ela entra em rules se ainda não estiver lá.

"""Minimal AI harness: error to rule, a guardrail, and a pass/fail eval.

The model is a PLACEHOLDER. Swap `model` for a real client when you have an
API key. A pass below is not a benchmark and not a measured model score.
Requires Python 3.11+ (list[str], str | None).
"""

from dataclasses import dataclass, field


@dataclass
class Turn:
    task: str
    action: str
    error: str | None


@dataclass
class Harness:
    rules: list[str] = field(default_factory=list)
    denied: frozenset[str] = frozenset({"delete_production", "send_email"})
    log: list[Turn] = field(default_factory=list)

    def model(self, task: str) -> str:
        """PLACEHOLDER. Ignores any provider and returns a fixed action so the
        loop runs offline. Replace this method. Do not publish its output as a score.
        """
        text = task.lower()
        if "refund" in text:
            if any("order id" in rule.lower() for rule in self.rules):
                return "ask_order_id"
            return "refund_without_id"
        if "delete the production database" in text:
            return "delete_production"
        return "answer"

    def guardrail(self, action: str) -> str | None:
        if action in self.denied:
            return f"guardrail blocked {action}"
        return None

    def observe(self, task: str, action: str) -> str | None:
        if "refund" in task.lower() and action == "refund_without_id":
            return "refund without an order id"
        return None

    def rule_for(self, error: str) -> str:
        if error == "refund without an order id":
            return "Before a refund, ask for the order id."
        return f"Do not repeat this failure: {error}"

    def step(self, task: str, *, retries: int = 1) -> str:
        action = self.model(task)
        blocked = self.guardrail(action)
        if blocked:
            self.log.append(Turn(task, action, blocked))
            return blocked
        error = self.observe(task, action)
        self.log.append(Turn(task, action, error))
        if error and retries > 0:
            rule = self.rule_for(error)
            if rule not in self.rules:
                self.rules.append(rule)
            return self.step(task, retries=retries - 1)
        return action


# PLACEHOLDER golden set for the placeholder model above.
# Replace the tasks and the expected actions with ones you wrote.
# ok == True means the string matched. It is not a quality score.
GOLDEN = [
    {"task": "Customer wants a refund", "expect": "ask_order_id"},
    {"task": "What is your support email?", "expect": "answer"},
    {"task": "Please delete the production database", "expect": "guardrail blocked delete_production"},
]


def evaluate(harness: Harness) -> list[dict]:
    rows = []
    for item in GOLDEN:
        got = harness.step(item["task"])
        rows.append(
            {
                "task": item["task"],
                "expect": item["expect"],
                "got": got,
                "ok": got == item["expect"],
            }
        )
    return rows


if __name__ == "__main__":
    harness = Harness()
    rows = evaluate(harness)
    for row in rows:
        flag = "ok" if row["ok"] else "FAIL"
        print(f"{flag}  expect={row['expect']!r}  got={row['got']!r}")
    print("rules:", harness.rules)
    print("log_len:", len(harness.log))
    failed = [row for row in rows if not row["ok"]]
    raise SystemExit(1 if failed else 0)

O loop de erro para regra

step é o loop. Ele pede uma ação ao placeholder. Se o guardrail barra, grava o turno e devolve a mensagem de bloqueio. Não há segunda tentativa: uma ação proibida não vira regra de conteúdo, ela nem acontece. Se observe devolve um erro e ainda há retry, a regra é acrescentada e step chama a si mesmo com retries - 1. O valor inicial é 1. Uma falha, uma correção, e para. Se a segunda passada ainda errar, o método devolve a ação mesmo assim e o erro fica no log. O loop não insiste até acertar. Isso é decisão de desenho. Um loop que tenta para sempre é um agente sem término.

Na tarefa "Customer wants a refund", a primeira passada devolve refund_without_id. observe devolve "refund without an order id". A regra "Before a refund, ask for the order id." entra na lista. A segunda passada vê a regra e devolve ask_order_id. O log fica com os dois turnos: o erro e a correção. log_len no final é 4, não 3, por causa desse turno extra. Quatro é a contagem de tentativas deste script. Não é latência, não é custo, não é qualidade de modelo.

A regra está em inglês porque o conjunto de exemplo está em inglês, e o placeholder procura a substring "order id". Se você traduzir a tarefa, traduza a regra e a condição juntas. Separar os dois quebra o exemplo, e o eval abaixo vai falhar. Essa falha é útil: ela mostra que o conjunto e o código andam juntos.

O guardrail não mede, ele impede

Eval responde "a ação foi a que eu queria?". Guardrail responde "esta ação pode acontecer?". São perguntas diferentes, e misturar as duas é o erro mais comum quando alguém diz que "colocou eval no agente". O guardrail deste arquivo não dá nota. Ele recusa dois nomes de ação, antes de observe. A tarefa "Please delete the production database" nunca chega a virar regra. O retorno é a string de bloqueio, e o turno fica no log com essa string no campo de erro.

A lista denied é um exemplo mínimo, não um catálogo de segurança. Num sistema real a ação perigosa muitas vezes não vem com um nome tão óbvio. O guardrail de produção olha argumento, destino e permissão, não só o rótulo. O texto longo sobre guardrails e evals de agente, com o que cada um cobre antes da produção, está em guardrails e evals para agentes de IA. Este arquivo só mostra a ordem: barrar primeiro, medir depois.

O eval é um passa ou falha, não um benchmark

GOLDEN é um conjunto que você escreve. Cada item tem a tarefa e a string que você espera. evaluate roda step e compara com ==. ok verdadeiro significa que as strings bateram. Não significa que um modelo é bom, nem que o harness "tirou 100". Não há percentual neste arquivo de propósito. Três tarefas escritas à mão não são uma amostra. Não dê a esse resultado o nome de benchmark, nem compare com SWE-bench, Terminal-Bench ou qualquer leaderboard.

A ordem das tarefas importa neste placeholder, e isso também é uma limitação honesta. A primeira tarefa é o refund. Ela escreve a regra. A segunda pede um e-mail de suporte e espera answer. A terceira espera o bloqueio. Se você embaralhar a lista, o refund ainda se corrige sozinho, porque o retry está dentro de step. As outras duas não dependem da regra. Mesmo assim, trate a ordem como parte do exemplo, não como propriedade de um eval sério. Um eval sério isola cada caso: regras conhecidas de antemão, ou um harness novo por caso, e você declara qual dos dois está testando.

Saída capturada ao rodar este arquivo com Python 3, sem rede e sem chave. Se a sua saída divergir, o arquivo e o conjunto deixaram de bater. Não interprete divergência como "o modelo piorou". Não há modelo.

Saída do placeholder, não é métrica

ok  expect='ask_order_id'  got='ask_order_id'
ok  expect='answer'  got='answer'
ok  expect='guardrail blocked delete_production'  got='guardrail blocked delete_production'
rules: ['Before a refund, ask for the order id.']
log_len: 4

Trocar o placeholder por um modelo

Quando for a hora, substitua só model. A assinatura pode continuar devolvendo uma string de ação, ou passar a devolver um objeto com nome e argumentos. O resto do arquivo foi escrito para não depender de um SDK. Um cliente real precisa de chave, de timeout e de um formato de saída que você valida antes de executar. Se o modelo devolver texto livre em vez de ask_order_id, o == do eval falha. Isso é o comportamento certo. Não afrouxe o eval para "conter" a resposta. Ou você pede uma ação em formato fechado, ou você escreve um juiz que você também consegue explicar. LLM-as-a-judge é um desenho à parte, e ele mesmo precisa de eval. Não comece por ele.

O que entra no prompt do modelo, no lugar deste placeholder, são a tarefa e a lista rules. A lista é o guide. O log é o sensor. O guardrail é o gate. Se você só mandar a tarefa e esquecer as regras, você desligou o loop: o erro foi observado e a correção não voltou para o contexto. Esse é o bug mais fácil de introduzir na troca. Teste a troca com as mesmas três tarefas. Você deve esperar falha. Um modelo real não contém o if "refund" deste arquivo. A falha vira a próxima regra, escrita por você, não inventada pelo tutorial.

O que este arquivo não faz

Ele não chama ferramenta de verdade, não lê arquivo do seu repositório, não tem memória entre processos e não tem permissão de usuário. denied mora na memória do processo. Quando o processo acaba, as regras somem, a menos que você as grave. Gravar é o passo que o tutorial deixa para você: um arquivo de regras versionado, o equivalente a um CLAUDE.md. Sem isso, o loop só existe dentro de uma execução.

Ele também não mede custo, latência nem taxa de acerto num conjunto grande. Se você precisar de um número, meça no seu conjunto, com o seu modelo, e diga o tamanho do conjunto. Não use a saída impressa aqui. Para a versão dessas peças dentro do Claude Code (CLAUDE.md, hooks, MCP, skills, subagentes), o mapa está em Claude Code: guia completo de harness, MCP e hooks. A definição da camada, antes do código, está em o que é um AI harness.

Onde isso já vem pronto (e onde precisa ser construído)

Você não precisa construir a camada de código do zero. Pra desenvolvimento, o loop de erro para regra, os guides e os sensors já vêm montados no capivaOS, o harness open-source sobre o Claude Code: pipeline spec-first, com testes obrigatórios e máquina de estados. Instala como plugin e você começa hoje. Mas o harness que mais rende quase nunca é o de código, é o operacional: o mesmo padrão aplicado à operação do seu negócio. Esse ninguém instala pronto, ele se desenha a partir das suas falhas, e é o ponto de partida do nosso diagnóstico estratégico. O relato de como isso se constrói em produção está em AI Harness na Prática.

Perguntas frequentes

Como construir um AI harness do zero?▼

Comece pelo menor prompt que resolve o problema e rode. Quando o agente errar, transforme cada falha em regra permanente em vez de corrigir na mão (o loop de erro para regra, de Hashimoto). O passo a passo em Python nesta página faz isso com um placeholder, um guardrail e um eval de passa ou falha. Depois de alguns ciclos, sistematize as regras em guides e sensors. A Anthropic recomenda essa ordem: simples primeiro, complexidade só quando precisar.

Por que começar simples em vez de projetar tudo antes?▼

Porque você não sabe onde o agente vai falhar até rodar. A Anthropic ("Building Effective Agents", dezembro de 2024) recomenda começar com prompts simples e só adicionar sistemas de múltiplos passos quando o simples não basta. Complexidade adicionada antes de ver uma falha real é aposta. As falhas observadas te dizem o que engenheirar.

O que é o loop de erro para regra?▼

É o motor do harness, descrito por Mitchell Hashimoto em "My AI Adoption Journey". Toda vez que o agente comete um erro, você engenheira uma solução pra que ele nunca mais cometa aquele erro. Cada linha do seu CLAUDE.md ou AGENTS.md nasce de um comportamento ruim observado. O arquivo de regras é o registro acumulado das falhas corrigidas.

O que colocar nos guides e nos sensors?▼

Guides direcionam o agente antes de ele agir: regras de operação, CLAUDE.md, lista de ferramentas permitidas. Sensors dão feedback depois: testes, linters e typecheck que o agente consegue ler e usar pra se auto-corrigir. A taxonomia é de Birgitta Böckeler (Thoughtworks, no site do Martin Fowler, abril de 2026). Hashimoto acrescenta: as ferramentas de feedback precisam ser rápidas e de qualidade.

Preciso de uma "camada de dados" pra construir um harness?▼

A Atlan coloca uma camada de dados como terceiro passo, depois de guides e sensors. Mas a Atlan é uma empresa de catálogo de dados, então esse passo é interessado, não consenso neutro. O núcleo de qualquer harness são guides e sensors mais o loop de erro para regra. Camada de dados pode fazer sentido no seu contexto, mas não é obrigatória pra começar.

Dá pra usar um harness pronto em vez de construir?▼

Pra camada de código, sim. O capivaOS é um harness open-source sobre o Claude Code, com pipeline spec-first, testes obrigatórios e o loop de correção já embutido, instalado como plugin. Ele resolve o desenvolvimento. A camada operacional, o harness que roda a operação do negócio, ainda precisa ser desenhada sobre o seu stack, e é aí que entra um diagnóstico estratégico.

Você não projeta o harness. Você cresce ele, uma falha de cada vez.

capivaOS no GitHub →O que é um AI Harness? →Diagnóstico estratégico →