Deixar um agent rodar código é um problema de contenção, não de confiança
Pare de perguntar se você confia no agent. A pergunta certa é o que acontece quando ele roda a coisa errada, e a resposta é um sandbox que assume que ele vai.
Apollo Space Research
Apollo Space
Um agent escreve um script Python de cinco linhas para parsear uma planilha, roda, e te entrega uma tabela limpa. Útil. Agora imagine o mesmo agent, num dia ruim, escrevendo um script que lê toda credencial na máquina e as posta para um endereço que ele acabou de inventar. Mesmo agent. Mesma permissão para rodar código. A única coisa entre os dois desfechos é o que o código consegue alcançar quando roda.
Essa lacuna é todo o assunto deste post. A maioria dos times a aborda fazendo a pergunta errada.
A pergunta errada é: confio neste agent o suficiente para deixá-lo rodar código? Você nunca vai obter uma resposta satisfatória, porque o agent é não-determinístico e a falha que você teme é a que ele ainda não te mostrou. Deixar um agent executar é um problema de contenção, não de confiança. O trabalho não é ter certeza de que ele se comporta. O trabalho é fazer com que mau comportamento não custe nada.
A forma ingênua: rodar onde tudo está
A forma óbvia de dar a um agent o poder de rodar código é dar a ele um shell. Ele já vive num servidor. O servidor tem Python, Node, uma conexão de rede e um conjunto de variáveis de ambiente. Então você deixa o agent dar shell out, e o observa trabalhar, e por uma tarde é mágico.
Aí você pensa no que aquele shell pode tocar.
Ele pode tocar toda variável de ambiente no processo, o que na maioria dos servidores significa a senha do banco de dados, as chaves de API de terceiros, as credenciais de nuvem. Ele pode abrir um socket para qualquer lugar na internet, e qualquer lugar na sua rede privada também. Ele pode ler o filesystem que o host consegue ler, incluindo dados de outros tenants se você é um produto multi-tenant. Ele pode girar num loop infinito e travar uma CPU até a máquina cair. Nada disso exige que o agent seja malicioso. Exige que o agent esteja errado uma vez, ou que seja convencido a estar errado por um prompt enterrado num documento que ele foi pedido para resumir.
A configuração ingênua tem um único ponto de falha e é o julgamento do agent. Você apostou a empresa num modelo não cometer um erro. Essa não é uma aposta que você pode vencer em escala; é uma aposta que você perde eventualmente e em silêncio. No dia em que ela falha, não há fronteira para pegá-la, porque você construiu o sistema como se a fronteira fosse o bom senso do agent.
Um shell no host é o equivalente, para agents, a rodar o programa de um estranho como root. Não fazemos isso com estranhos. Não deveríamos fazer com agents tampouco, e pela mesma razão: o perigo não é quem eles são, é o que eles podem alcançar.
O formato da correção: um lugar onde rodar a coisa errada é tedioso
Aqui está o reenquadramento sobre o qual o resto do post se constrói. Você para de tentar garantir que o código é seguro, e começa a garantir que código inseguro não consegue fazer nada interessante. Você move a execução para fora do host e para dentro de uma caixa construída de modo que o pior caso seja um dar de ombros.
Deixar um agent executar é um problema de contenção, não de confiança. Então você constrói um container, um de verdade, e faz quatro promessas sobre ele, cada uma das quais remove uma das coisas que o shell ingênuo poderia alcançar.
As quatro promessas são: isolamento (o código roda na sua própria caixa, não na sua), caps de recursos (a caixa não pode crescer além de sua cota), nenhum segredo ambiente (a caixa começa sem nada que valha a pena roubar) e efêmera por default (a caixa é destruída no momento em que a tarefa termina, então nada sobrevive para ser explorado depois). Pegue uma de cada vez, porque cada uma fecha uma porta específica que a versão ingênua deixou aberta.
Isolamento: o código roda na sua própria caixa, não na sua
A falha ingênua era que o código do agent rodava no seu processo, no seu host. O que quer que o host pudesse ver, o código podia ver. O isolamento quebra essa linha.
O instinto ingênuo aqui é um mais suave que de fato não funciona: rodar o código no mesmo processo mas “com cuidado”, validar o script primeiro, escaneá-lo por chamadas perigosas, recusar qualquer coisa que importe a biblioteca errada. Isto é uma denylist, e denylists perdem. Há formas demais de ler um arquivo ou abrir um socket; você nunca vai enumerar todas, e a que você esquecer é a que machuca. Pior, você agora acoplou sua segurança à sua capacidade de prever todo padrão perigoso de antemão, a mesma aposta perdedora de confiar no agent, só vestida de engenharia.
A correção real é uma parede, não um filtro. O código roda num sandbox separado, seu próprio container ou microVM, com sua própria visão de kernel, sua própria tabela de processos, seu próprio mount de filesystem. Ele não lê seu ambiente porque não está no seu ambiente. Quando ele pergunta ao sistema operacional “que arquivos existem”, a resposta é os arquivos do sandbox, não os do host. A fronteira não é uma regra que o código concorda em seguir. É uma parede que o código fisicamente não consegue enxergar além.
A diferença entre uma denylist e uma parede é a diferença entre pedir ao ladrão que por favor não toque na prata e não colocar a prata no quarto. Uma depende do ladrão. A outra não.
Caps de recursos: a caixa não pode crescer além de sua cota
O isolamento impede o código de alcançar para fora. Não impede o código de ser caro. Um container isolado ainda pode girar um loop que come todo core, alocar memória até a máquina entrar em swap até morrer, ou dar fork até a tabela de processos encher. Isolamento é sobre onde o código pode alcançar; caps são sobre quanto ele pode consumir.
A versão ingênua é nenhum cap, você assume que o agent escreve código razoável, e na maioria das vezes ele escreve. Aí uma tarefa bate num input patológico, o loop nunca termina, e um único script descontrolado mata por fome toda outra tarefa no host. Agora seu sandbox protegeu seus segredos e derrubou seu serviço mesmo assim. Contiveram o vazamento, perderam o prédio.
Então toda caixa recebe uma cota escrita antes de começar: um teto de CPU, um teto de memória, um teto de quanto tempo ela tem permissão para rodar, um teto de processos e arquivos abertos. Bata no teto e a caixa é morta, não avisada, morta. O custo de uma tarefa descontrolada é exatamente uma tarefa, a que descontrolou. A razão pela qual isso importa mais para agents do que para código comum é que você não escreveu o código e não consegue prever seu formato. O loop infinito de um engenheiro humano é um bug que ele vai corrigir. O loop infinito de um agent é terça-feira. Você planeja para isso tornando-o barato.
Suponha que um script deveria terminar num segundo e ainda está rodando depois de, digamos, trinta. O agent não ganhou nada nesses trinta segundos, ele só achou um jeito de estar errado devagar. O cap transforma “errado devagar para sempre” em “errado, depois sumido”.
Nenhum segredo ambiente: a caixa começa sem nada que valha a pena roubar
Esta é a promessa que os times pulam, e é a que transforma uma brecha contida num não-evento.
Pense no que “segredos ambiente” significa. Num servidor normal, as credenciais vivem no ambiente, a URL do banco de dados, os tokens de API, as chaves de nuvem, sentadas em variáveis de ambiente ou num arquivo montado, disponíveis a qualquer processo que olhar. Elas são ambiente: presentes em todo lugar, por default, para tudo. Conveniente, e exatamente a coisa que você menos quer que um pedaço de código escrito por agent consiga ler.
A correção ingênua é passar ao agent apenas os segredos de que ele precisa para esta tarefa. Melhor, mas os segredos ainda estão na caixa, sentados na memória, legíveis pelo próprio código em que você não confiou o suficiente para rodar no host. Se aquele código os exfiltrar, dar escopo te comprou um raio de explosão menor, não zero.
A correção real é que a caixa começa vazia. Nenhuma credencial vive dentro dela. Quando a tarefa genuinamente precisa alcançar um sistema real, puxar uma linha, chamar uma API, ela não lê uma chave do seu ambiente; ela pede a um broker fora do sandbox, que guarda os segredos reais, checa que esta tarefa tem permissão para esta ação única, e ou a faz em nome da tarefa ou devolve uma credencial com escopo de uma capability e que expira em minutos. O segredo nunca entra na caixa em que ele não pode confiar. E porque a caixa é jogada fora quando a tarefa termina, mesmo uma credencial vazada chega morta, ela expirou junto com o sandbox que brevemente a segurou.
O princípio por baixo: um segredo que não está lá não pode ser roubado. O lugar mais seguro para uma credencial é fora do quarto onde código não-confiável roda, entregue uma capability estreita de cada vez, e sumido no momento em que o trabalho acaba.
Efêmera por default: a caixa é destruída quando a tarefa termina
A última promessa é a mais barata de enunciar e a mais fácil de errar: quando a tarefa termina, a caixa morre. Completamente. Filesystem, memória, processo, sumidos.
A otimização tentadora é manter a caixa por perto. Subir um sandbox novo custa uma batida de latência, então por que não reusar um quente para a próxima tarefa? Porque reúso é como o estado vaza entre tarefas. Uma tarefa escreve um arquivo temporário com os dados de um cliente; a próxima tarefa, para um cliente diferente, o encontra sentado ali. Uma tarefa deixa um processo rodando; a próxima o herda. Um sandbox de vida longa lentamente acumula exatamente o estado ambiente que você construiu o sandbox para evitar. Você reconstruiu o host, menor.
Efêmera significa que o contrato é: toda tarefa recebe uma caixa limpa, e a caixa não sobrevive a nada. Nenhum arquivo escrito numa tarefa é visível a outra. Nenhum processo iniciado numa sobrevive à próxima. O que quer que o código tenha feito ao seu mundo morre com o mundo. Isto é o que faz as outras três promessas valerem ao longo do tempo, isolamento, caps e nenhum-segredo-ambiente são garantias sobre uma única execução, e a efemeridade é a garantia de que uma única execução é tudo o que você jamais terá. A caixa é um fósforo, não uma vela. Ela queima uma vez, para uma tarefa, e depois não sobra nada para pegar fogo.
Há um bônus silencioso: um sistema onde toda execução começa do mesmo estado limpo é um sistema sobre o qual você consegue raciocinar. O mesmo código com o mesmo input faz a mesma coisa, porque não há sobra da última vez para mudar a resposta. Contenção e reprodutibilidade acabam sendo a mesma parede, vista de dois lados.
A virada: isto é o que te deixa dizer sim
Tire os containers e os brokers e aqui está o que de fato está acontecendo. A razão pela qual a maioria dos times não deixa um agent rodar código não é que eles o acham incapaz. É que eles não conseguem limitar o lado ruim, então a única resposta segura é não, e um colega a quem você nunca pode dizer sim não é um colega, é uma demo.
O sandbox é o que converte o não num sim. Não porque torna o agent confiável, nada torna um sistema não-determinístico confiável, e perseguir isso é uma armadilha. É porque o sandbox torna estar errado acessível. Quando o pior que o código pode fazer é queimar sua própria caixa descartável, você para de precisar ter certeza de que ele está certo. Você pode deixá-lo tentar. Você pode deixá-lo estar errado, e aprender com o erro, e tentar de novo, numa velocidade que seria imprudente se cada erro pudesse alcançar seus segredos ou seus outros clientes.
Essa é toda a mudança. Confiança é um binário que você nunca consegue resolver honestamente. Contenção é uma propriedade que você constrói, mede e na qual confia. Escolhemos construir a propriedade, isolamento para que o código rode na sua própria caixa, caps para que ele não possa crescer além de sua cota, nenhum segredo ambiente para que não haja nada dentro que valha a pena roubar, efemeridade para que nada sobreviva para ser explorado. Quatro promessas, uma fronteira, e do outro lado dela um agent que finalmente tem permissão para fazer trabalho de verdade.
Isto é o que estamos construindo na Apollo: um lugar onde um agent pode executar, falhar barato e tentar de novo, porque a pergunta nunca foi se você confia nele. A pergunta sempre foi o que ele consegue alcançar quando roda. Acerte a fronteira e a confiança se resolve sozinha.
A Apollo cuida da operação repetitiva da sua empresa pro seu time não precisar.
Entre na lista de espera: acesso antecipado, preço de usuário fundador e um lugar na primeira fila enquanto a gente constrói.
Entrar na lista de esperaO imposto oculto dos agents em paralelo é um diamante de migrations
Seis agents escrevendo para um schema conflitam no banco de dados, não no código, e a CI morre em "multiple heads".
EngenhariaUm orchestrator que não sobrevive ao próprio crash não é um
Um crash que apaga o raciocínio do orchestrator perde a única coisa que você não consegue reconstruir.
EngenhariaColoque um portão determinístico na frente do seu revisor mais esperto
A pega-defeito mais barata é um script burro que checa se duas branches mergeadas ainda sobem antes de qualquer julgamento.