Pular para o conteúdo

Destravando Performance do Strix

Otimizando a Arquitetura Multiagente do Strix
17 de dezembro de 2025 por
Destravando Performance do Strix
Anderson Torres

Quando comecei a avaliar o Strix mais a fundo, a primeira impressão foi ótima: a proposta de um sistema de teste de segurança multiagente, capaz de descobrir e validar vulnerabilidades de forma autônoma, é exatamente o tipo de evolução que a área precisa. A arquitetura, no papel e no uso, é bem montada — agentes conseguem criar subagentes, coordenar ações via troca de mensagens e executar fluxos complexos de pentest.

Só que, conforme eu fui aumentando a escala (aplicações maiores, mais caminhos, mais agentes paralelos), uma coisa começou a incomodar: os agentes pareciam passar mais tempo esperando do que trabalhando.

Essa é a história de como esse gargalo apareceu, como ele foi identificado na raiz e o que mudou quando a arquitetura finalmente passou a entregar o paralelismo que prometia.

A Descoberta: Agentes Parados na fila

O problema ficou gritante durante um pentest profundo em uma aplicação web grande. Eu configurei o Strix para criar agentes especializados por categoria de vulnerabilidade — um para SQL Injection, outro para validação de XSS, outro para tentativas de bypass de autenticação e por aí vai. Em teoria, esses agentes deveriam avançar em paralelo, cada um evoluindo no seu trilho. Na prática, não foi isso que aconteceu.

Quando eu olhei a linha do tempo de execução, o padrão era estranho: os agentes pareciam concluir as etapas de forma sequencial, e não concorrente. Um finalizava uma chamada ao LLM, aí o próximo “andava”, depois o próximo… e esse atraso crescia em linha reta conforme aumentava o número de agentes. O tipo de scan que deveria levar minutos começou a levar horas.

O modelo de threading estava correto: cada agente em uma thread separada, cada um com seu event loop asyncio. Então não era “falta de paralelismo” no Python em si. O problema estava mais abaixo, no jeito como as requisições ao modelo de linguagem estavam sendo controladas.

O Gargalo: Uma Fila Global de Requisições

Todo agente do Strix precisa conversar com um LLM para decidir o próximo passo, analisar evidências e planejar a estratégia. Para evitar estourar rate limit do provedor, existe uma fila global de requisições. Conceitualmente, isso é uma boa ideia. O problema é que o padrão estava conservador demais — conservador a ponto de estrangular o sistema.

Olha o núcleo do que encontrei no controle da fila:

class LLMRequestQueue:
    def __init__(self, max_concurrent: int = 1, delay_between_requests: float = 4.0):
        self.max_concurrent = max_concurrent
        self.delay_between_requests = delay_between_requests
        self._semaphore = threading.BoundedSemaphore(max_concurrent)

O veneno estava nos defaults: max_concurrent=1 permitia apenas uma requisição por vez; delay_between_requests=4.0 enfiava quatro segundos de atraso entre uma chamada e outra. Com dez agentes tentando avançar ao mesmo tempo, o resultado era uma serialização perfeita.

Pra visualizar: imagine 10 agentes, cada um precisando fazer 5 chamadas ao LLM para concluir sua missão. Isso vira 50 requisições processadas uma por vez, com 4 segundos “mortos” entre elas. Só aí dá mais de 3 minutos de espera pura — sem contar o tempo de resposta do LLM. Em cenários reais, com mais iterações e conversas maiores, isso escala pra horas fácil.

A Correção: Destravando o Paralelismo

Depois que o gargalo ficou claro, a solução foi quase simples: as APIs modernas de LLM aguentam várias requisições simultâneas, e a própria fila já tinha sido desenhada para suportar isso. Faltava tornar o comportamento ajustável e usar parâmetros mais realistas.

O controle passou a ser configurável via variáveis de ambiente, permitindo que cada ambiente ajuste conforme rate limit do provedor:

rate_limit_delay = os.getenv("LLM_RATE_LIMIT_DELAY")
if rate_limit_delay:
    delay_between_requests = float(rate_limit_delay)

rate_limit_concurrent = os.getenv("LLM_RATE_LIMIT_CONCURRENT")
if rate_limit_concurrent:
    max_concurrent = int(rate_limit_concurrent)

Com LLM_RATE_LIMIT_CONCURRENT=5 e LLM_RATE_LIMIT_DELAY=0.5, o Strix passou a permitir cinco chamadas ao mesmo tempo, com apenas meio segundo entre requisições. O impacto foi imediato e bem absurdo.

Ganhos Reais de Performance

Pra medir de forma objetiva, eu rodei uma bateria de testes em uma aplicação web de porte médio: cerca de 50 endpoints, e o Strix configurado para gerar 8 agentes especializados (2 de reconhecimento, 3 de descoberta, 2 de validação e 1 de reporte).

Antes da otimização:

  • Tempo total: 47 minutos
  • Tempo médio de espera por agente: 12,3 minutos
  • Chamadas ao LLM: 142 requisições
  • Processamento sequencial: 95% das requisições em fila

Depois da otimização (LLM_RATE_LIMIT_CONCURRENT=5, LLM_RATE_LIMIT_DELAY=0.5):

  • Tempo total: 11 minutos
  • Tempo médio de espera por agente: 2,1 minutos
  • Chamadas ao LLM: 142 requisições (mesma carga)
  • Processamento sequencial: 28% das requisições em fila

Resultado: 4,3x mais rápido com a mesma carga de trabalho. E o tempo de espera caiu de 12,3 para 2,1 minutos por agente — uma redução de 83%.

Além da Fila: Outras Otimizações que Valeram a Pena

A fila global era o grande vilão, mas eu também identifiquei outras áreas onde ajustes menores renderam ganhos relevantes.

Quando um agente cria subagentes, ele pode repassar o histórico de conversa completo. Isso ajuda contexto, mas copiar um histórico grande custa caro — e muitas vezes o subagente não precisa de tudo aquilo, só da tarefa e de um resumo do que importa.

# Antes: sempre herdando contexto completo
inherited_messages = agent_state.get_conversation_history()  # Pode ter 100+ mensagens

# Depois: herança seletiva
if inherit_context:
    inherited_messages = agent_state.get_conversation_history()
else:
    inherited_messages = []  # Começa do zero

Ao usar inherit_context=False para agentes que não precisam do contexto completo, o tempo de inicialização caiu algo entre 40% e 60% nesses casos. E de brinde, a memória consumida diminuiu e a etapa de “compressão” do histórico ficou mais rápida.

Frequência de Compressão de Memória

Cada requisição ao LLM dispara uma compressão/resumo do histórico para respeitar limite de tokens. Eu notei que o sistema estava recomprimindo em toda chamada, mesmo quando o histórico não tinha mudado de forma relevante.

Ainda falta um cache inteligente (e faz sentido isso entrar em roadmap), mas só de otimizar o algoritmo de compressão, já deu pra reduzir o tempo dessa etapa em cerca de 30% em conversas típicas. O ganho grande mesmo vai vir quando a compressão for feita só quando realmente “precisar”.

Paralelismo na Execução de Ferramentas

As ferramentas dentro do sandbox são executadas via HTTP contra um tool server no contêiner Docker. Isso já é assíncrono, mas nem tudo estava sendo explorado em paralelo quando era possível.

Exemplo: um agente testando SQLi quer tentar payloads em múltiplos endpoints. Em vez de ir um a um, dá para disparar chamadas independentes em paralelo, quando não há dependência entre elas. Isso reduz ainda mais o tempo morto.

Impacto em Scans de Verdade

Esse tipo de melhoria não é só “benchmark bonito”. Ela muda o resultado prático do teste de segurança:

Cobertura mais completa: com paralelismo real, dá pra criar mais agentes especializados sem medo do tempo explodir. Um scan que levava 2 horas com 5 agentes pode cair para 30 minutos com 15 agentes — triplicando cobertura em um quarto do tempo.

Melhor uso de recursos: custo de infraestrutura costuma estar ligado ao tempo do scan. Se você ganha 4x em velocidade, você ganha 4x em eficiência de custo. Em organizações que rodam scans frequentes, isso vira economia real.

Experiência melhor para devs: segurança precisa caber no fluxo de desenvolvimento. Se um “quick scan” cai de 20 minutos para menos de 5, vira plausível rodar em pull request sem atrapalhar a vida.

Recomendações de Configuração

Com base nos testes que eu fiz, aqui vão configurações práticas para cenários comuns:

Para OpenAI GPT-4/GPT-5:

export LLM_RATE_LIMIT_CONCURRENT=10
export LLM_RATE_LIMIT_DELAY=0.1

Para Anthropic Claude:

export LLM_RATE_LIMIT_CONCURRENT=5
export LLM_RATE_LIMIT_DELAY=0.5

Para modelos locais (Ollama, LMStudio):

export LLM_RATE_LIMIT_CONCURRENT=3
export LLM_RATE_LIMIT_DELAY=0.0

Modo conservador (rate limits mais rígidos):

export LLM_RATE_LIMIT_CONCURRENT=2
export LLM_RATE_LIMIT_DELAY=1.0

O ponto aqui é simples: ajuste conforme seu provedor e seus limites. Em geral, 5–10 concorrentes costuma ser tranquilo em provedores modernos, mas vale validar com o rate limit real do seu plano/conta.

Próximos Passos (O Que Eu Gostaria de Ver Evoluir)

Depois de destravar esses gargalos, fica claro que ainda dá pra ir além:

  • Filas por agente ou por modelo, ao invés de uma fila global única, para melhorar justiça e evitar que um agente “prenda” o resto.
  • Sumarização inteligente por tarefa, em vez de herdar histórico inteiro.
  • Spawn preditivo, analisando a estrutura do app e criando agentes de forma mais estratégica.
  • Batch de tool calls, reduzindo overhead de múltiplas chamadas HTTP pequenas.

Conclusão

Otimização de performance em sistemas distribuídos quase sempre é esse jogo: equilibrar segurança e estabilidade com velocidade. O controle conservador de requisições ao LLM foi uma escolha compreensível no começo — mas conforme o Strix foi escalando, isso virou o principal gargalo, silencioso e cruel.

Depois de ajustar concorrência e delays de forma configurável (e com defaults mais realistas), o Strix finalmente começou a operar do jeito que a arquitetura multiagente promete: scans 4x mais rápidos, 83% menos tempo parado, e possibilidade real de escalar número de agentes sem o tempo explodir.

Se você está usando o Strix e ainda não mexeu em LLM_RATE_LIMIT_CONCURRENT e LLM_RATE_LIMIT_DELAY, mexe nisso. Sério. Começa com valores recomendados pro seu provedor e ajusta conforme o seu rate limit e a sua necessidade. A chance de você se assustar positivamente com o ganho é bem alta.

O futuro do teste de segurança é rápido, abrangente e automatizado. Essas melhorias são um passo bem concreto nessa direção.

Strix por Dentro: Um Mergulho Profundo de um Especialista em Segurança em Pentest com IA