Quem coloca um agente de IA em produção dentro de um CRM ou de um ERP descobre rápido que a maior parte do trabalho fica fora do modelo. O nome que o mercado deu para essa parte é harness: tudo que fica em volta da LLM e faz ela funcionar dentro de um sistema de verdade.
Este post explica o conceito peça por peça e depois mostra cada uma no código de um agente SDR de imobiliária, o mesmo de um vídeo anterior do canal. A ideia é que, entendendo o que é harness, colocar um agente em produção vire um projeto de dias em vez de meses.
A LLM sozinha só troca texto
Tudo começa pelo modelo. A LLM está cada vez melhor e cada vez mais commodity, mas sozinha ela faz uma coisa só: recebe texto, processa texto e devolve texto.
Quando um lead escreve “quero um apê de 2 quartos na Vila Mariana até R$ 650 mil”, o modelo pode responder que a intenção é buscar imóveis e que quer chamar uma ferramenta de busca com esses filtros. Ele escreve isso. Quem chama a API de imóveis é o seu sistema. O modelo também não lembra da conversa anterior, porque cada chamada começa do zero, e não acessa banco, CRM ou agenda do corretor por conta própria.
É ali dentro que está a rede neural e todo o trabalho de deep learning. A LLM sozinha, porém, não é o seu agente nem o seu harness.
As peças do harness
Para usar o modelo num sistema de gestão, você precisa de oito peças em volta dele. Todas são código seu.
Contexto. São os prompts, as instruções, os dados do seu sistema e a memória da conversa. Muita gente chama a parte de dados de RAG. O harness junta tudo isso, transforma em texto e manda para a LLM.
Ferramentas. É a lista do que o modelo pode pedir para fazer: qual API chamar, qual tabela consultar, quais comandos estão disponíveis. O modelo recebe essa lista em texto e decide quando usar cada item.
Loop. Depois que o modelo decide, alguém precisa avançar. O harness tem um laço de execução: a LLM escolhe uma ferramenta e manda essa escolha para o sistema. O sistema executa, registra o que aconteceu e devolve o resultado. A LLM decide se já tem o suficiente ou se precisa de mais uma chamada.
No exemplo, o laço é um while (true) que só termina quando a política de encerramento manda sair.
O modelo não executa nada nesse laço. Quem executa é o harness, que no caso é o seu próprio sistema.
Orquestração. Quem atende cada pedido e cada troca de mensagem com o usuário? Num CRM ou ERP, o ideal é você orquestrar com base no que a IA devolve, usando especialistas e roteamento. Não precisa confiar cegamente no modelo. Cada lado faz o que faz melhor: a LLM interpreta linguagem, e o seu código determinístico, que você já controla, executa as regras de negócio.
Estado durável. A conexão cai, o provedor da LLM fica indisponível, a rede falha, a máquina reinicia, o pod crasha. O harness precisa guardar o estado de cada atendimento e conseguir voltar de onde parou. A série sobre execução durável aqui no blog detalha esse assunto.
Guardrails. Definem o que a LLM nunca pode fazer e como o sistema valida o que entra e o que sai do modelo.
Observabilidade. Trace, custo e latência de cada chamada. O que a LLM decidiu, qual ferramenta chamou, o que voltou e por quê.
Evals. Como você sabe que uma mudança no prompt melhorou ou piorou o agente? Precisa medir antes de a ação ser executada ou de a resposta chegar ao usuário.
O harness no código: um agente SDR de imobiliária
O agente do exemplo atende leads de uma imobiliária pelo WhatsApp. Ele qualifica o lead, busca imóveis e agenda visitas com os corretores. O código dele é o harness: é ele que conversa com a IA. Os trechos abaixo são simplificados para mostrar cada peça.
[SkailFunction]
public async SkailTask Atender(string sessaoId, MensagemLead mensagem)
{
var config = await ObterConfiguracoesDaSessao(sessaoId); // regras da imobiliária, horários, limites
var anexos = await ProcessarAnexos(mensagem.Anexos); // PDF, áudio e foto viram texto
while (true)
{
var intencao = await DetectarIntencao(sessaoId, mensagem.Texto, anexos, config);
var resultado = await ExecutarFluxo(sessaoId, intencao, config);
var motivo = PoliticaDeEncerramento.Avaliar(intencao, resultado, config);
if (motivo is not null)
{
await EncerrarSessao(sessaoId, motivo);
break;
}
mensagem = await AguardarProximaMensagem(sessaoId);
}
}
O atendimento começa pelas configurações da sessão. É um conjunto de regras que o agente precisa obedecer, porque no fim ele é um sistema: horário de atendimento dos corretores, regiões que a imobiliária atende, quantas mensagens sem resposta encerram a conversa. Isso é carregado de forma determinística, com código seu, sem passar pelo modelo.
Anexos viram texto antes de entrar no contexto
Com a mensagem do usuário em mãos, o harness processa os anexos. Aqui também entra IA, mas do jeito que a gente viu no conceito: o sistema transforma tudo em texto e manda esse texto para o modelo.
A extração também usa IA, mas o harness decide o formato do texto, o que fica de fora e o que vai para o modelo do atendimento.
Um holerite em PDF passa por OCR, um áudio de 48 segundos dizendo “tenho FGTS pra entrada” passa por transcrição e a foto da CNH passa por um modelo de visão. O que chega ao modelo do atendimento são três linhas de texto com renda mensal, forma de entrada e documento. O harness decide o formato desse texto e o que fica de fora.
Até aqui, se o seu sistema já interage com IA, você provavelmente já tem um harness básico. Pegar um prompt, mandar para a LLM e usar o resultado para conversar com o usuário é harness. É um harness simples, com contexto e nada mais. As próximas peças são o que separa esse uso de um agente em produção.
A IA detecta a intenção, o código executa o fluxo
Dentro do loop, o harness manda para o modelo o texto do lead, as configurações e o contexto. O modelo devolve qual é a intenção daquela mensagem naquele contexto. Com a intenção em mãos, o harness executa um fluxo, que é código determinístico seu.
[SkailFunction]
public async SkailTask<ResultadoFluxo> ExecutarFluxo(string sessaoId, Intencao intencao, ConfigSessao config)
{
return intencao.Chave switch
{
"qualificar_lead" => await QualificarLead(sessaoId, intencao),
"buscar_imoveis" => await BuscarImoveis(sessaoId, intencao),
"agendar_visita" => await AgendarVisita(sessaoId, intencao, config),
"encerrar_atendimento" => await EncerrarAtendimento(sessaoId, intencao),
_ => await PedirEsclarecimento(sessaoId)
};
}
Pegue o fluxo de encerrar atendimento como exemplo. Ele está ligado à intenção encerrar_atendimento. Quem decide que a intenção é essa é a IA, com base no contexto que recebeu. Quem decide o que fazer com essa intenção é o seu código.
Depois do fluxo, entra a política de encerramento. Ela olha a intenção, o resultado do fluxo e a configuração carregada no início do atendimento. É lógica do sistema, sem modelo envolvido. Se a política entende que a sessão deve fechar, o harness grava o motivo, encerra a sessão e sai do loop. Se não, o resultado volta para o modelo escrever a resposta ao lead e o loop segue. O loop é um while (true): enquanto a política não manda sair, ele continua.
Esse é o ciclo completo do harness num exemplo concreto. O sistema trouxe informações do mundo externo para o modelo, o modelo decidiu a intenção, o código executou o que precisava e devolveu o resultado para o modelo.
Anatomia de uma tool
A outra forma de o modelo agir é chamando ferramentas diretamente. Uma tool no exemplo tem quatro partes:
- Nome, que o modelo usa para pedir a ferramenta.
- Descrição, um prompt curto que explica o que a ferramenta faz e quando usar.
- Contrato, com os parâmetros e os tipos que o modelo precisa preencher.
- Executar, o método que roda quando o modelo pede a ferramenta.
public sealed class ObterLeadTool(IApiInterna api) : ITool
{
public string Nome => "obter_lead";
public string Descricao => "Busca os dados atuais do lead: nome, telefone, faixa de preço, " +
"bairros de interesse e estágio da qualificação.";
public string Contrato => """{ "type": "object", "properties": { "leadId": { "type": "integer" } }, "required": ["leadId"] }""";
public Task<string> ExecutarAsync(JsonElement args, CancellationToken ct)
=> api.EnviarAsync(HttpMethod.Get, $"/api/leads/{args.GetProperty("leadId").GetInt32()}", ct);
}
As três primeiras partes vão para o modelo em forma de texto, porque é só isso que ele entende. Se o modelo decide usar a ferramenta, o harness chama o ExecutarAsync. Ele é uma chamada a uma API do próprio sistema, /api/leads, com os argumentos que o modelo pediu. O EnviarAsync concentra o tratamento de injeção, controle de acesso e segurança da chamada.
PUT /api/leads/4711 com os argumentos que o modelo devolveu.A tool atualizar_lead mostra um detalhe útil. A descrição dela diz “Atualiza a qualificação do lead. Use obter_lead antes e preserve os dados existentes.” O próprio contrato instrui o modelo a chamar uma ferramenta antes da outra. É mais uma forma de montar o loop de ferramentas: o modelo decide, o sistema executa e devolve, o modelo decide de novo. A chamada pode ir para os endpoints do seu sistema, como no exemplo, ou para qualquer API REST externa.
Com isso o código já tem contexto, ferramentas, loop e orquestração. Faltam estado durável e observabilidade.
Estado durável e observabilidade com o skail
Este post sai no blog da skail, e o skail é a plataforma de execução durável que a gente constrói aqui. O agente do exemplo roda sobre ele, e é nele que estão as duas peças que faltam.
O estado durável está nos atributos que aparecem no código. O Atender e o ExecutarFluxo são [SkailFunction], os métodos que orquestram o atendimento. Cada chamada ao modelo e cada ferramenta fica num [SkailCommand], o método que faz uma operação de I/O:
[SkailCommand]
public async SkailTask<Intencao> DetectarIntencao(string sessaoId, string texto, Anexos anexos, ConfigSessao config)
=> await _llm.ClassificarAsync(texto, anexos, config.Intencoes);
Com esses atributos, a execução ganha estado durável sem código extra. Se o atendimento para no meio por um problema de infraestrutura, ele volta exatamente de onde parou. Se precisa esperar dias ou semanas por uma resposta do lead ou de um corretor, a execução hiberna e libera a thread e o host enquanto espera. Isso vale para o código rodando em contêiner, na nuvem ou no servidor onde o seu sistema já roda.
A observabilidade segue o mesmo caminho. Tudo que o agente faz, decide e cada passo por onde passa fica registrado e aparece no Monitor do skail, numa linha do tempo por execução.
Somando tudo, o harness do agente fica com execução durável, loop, execução de ferramentas, detecção de intenção e orquestração baseada nos fluxos. Tem código determinístico no atendimento e dentro dos fluxos, e as integrações com o sistema atual passam pelas interfaces que o sistema já usa.
Alternativas de stack
O código do exemplo é curto porque usa o skail, mas nada disso depende dele. Para chamar ferramentas e manter a conversa, você pode usar o function calling do SDK da OpenAI, o tool use da Anthropic ou o equivalente do Google. Para o estado durável, precisaria de uma plataforma de execução durável ao lado, como Azure Durable Functions ou Cloudflare Workflows.
Tool calling: SDK da OpenAI (function calling), da Anthropic (tool use) ou do Google.
Estado durável: Azure Durable Functions, Cloudflare Workflows ou outra plataforma de execução durável.
Observabilidade: integrar traces e custos de cada chamada por conta própria.
[SkailFunction] no fluxo do agente e [SkailCommand] em cada chamada ao modelo e a cada tool.
Retomada, hibernação e linha do tempo no Monitor vêm junto.
O código roda no seu host, ao lado do CRM ou do ERP.
No exemplo, o skail entregou essas peças num lugar só, então não foi preciso combinar o SDK de um provedor com a plataforma de execução durável de outro.
Resumo para consulta
| Peça | Pergunta que ela responde | No agente da imobiliária |
|---|---|---|
| Contexto | O que o modelo precisa saber agora? | Configurações da sessão, histórico e anexos em texto |
| Ferramentas | O que o modelo pode pedir para fazer? | obter_lead, atualizar_lead, busca de imóveis |
| Loop | Como o agente avança até terminar? | while (true) até a política de encerramento |
| Orquestração | Quem decide o que fazer com a resposta do modelo? | Fluxo por intenção em código determinístico |
| Estado durável | E se cair no meio? | [SkailFunction] e [SkailCommand] |
| Guardrails | O que o modelo nunca pode fazer? | Validação de entrada e de saída |
| Observabilidade | O que aconteceu e quanto custou? | Linha do tempo no Monitor |
| Evals | Mudou para melhor ou para pior? | Medição antes de a resposta chegar ao lead |
Perguntas frequentes
Se eu só mando um prompt para a LLM e mostro a resposta, isso já é harness? É um harness básico, com contexto e nada mais. Ele funciona para uma pergunta e uma resposta. Para um agente que consulta o CRM, agenda visita e espera o lead responder, faltam ferramentas, loop, orquestração e estado durável.
Por que deixar a decisão do fluxo no código em vez de deixar o modelo decidir tudo? Porque num sistema de gestão as regras de negócio já existem e já funcionam. O modelo é bom para interpretar a mensagem do lead. O código é previsível, testável e fica sob o seu controle.
A ferramenta precisa chamar uma API do meu sistema? Pode chamar a API do seu sistema, um endpoint interno ou qualquer API REST externa. O que importa é a chamada passar pelo harness, onde ficam os guardrails e o registro.
Por onde começar
Olhe para o agente que você já tem, mesmo que seja um prompt e uma resposta. Liste as oito peças e marque quais existem hoje. Quase sempre o contexto está lá e o estado durável não. Comece pelo que mais dói em produção. Para ver o agente da imobiliária construído do zero, assista ao vídeo sobre criar um agente do zero no canal.
