DEV Community

Cover image for Não achei um framework Go production-ready para agentes de IA. Então construí um.
Willian R Moraes
Willian R Moraes

Posted on

Não achei um framework Go production-ready para agentes de IA. Então construí um.

Como construí um framework Go para agentes de IA em produção — e as decisões de arquitetura que realmente importam.


Quinze anos escrevendo software profissionalmente e nunca open sourcei uma linha de código.

Não é que eu não quisesse. É que é assim que funciona quando você constrói dentro de empresa — o código é deles, os problemas são específicos do domínio deles, e quando você finalmente tinha algo que poderia abstrair pra algo útil, já tinha passado pra próxima crise.

Dois meses atrás decidi mudar isso.

Estava construindo um agente de IA conversacional em Go. Precisava de um framework. Fui procurar e encontrei... Python. Mais Python. Uns repos Go abandonados em 2023. E muito "é só envolver o SDK da OpenAI" — conselho que funciona bem até você ter tráfego real e o agente começar a responder duas vezes para a mesma mensagem.

Nada production-ready. Nada com arquitetura de verdade. Nada que eu pudesse passar para um time e falar isso aguenta.

Então construí. O resultado é o eywa — framework Go para agentes de IA conversacionais, arquitetura hexagonal, v1.0.0, licença MIT, open source.

O nome vem de Avatar — Eywa é a rede neural que conecta todos os seres vivos em Pandora. A metáfora encaixou: um sistema que conecta LLMs, canais, memória e ferramentas num organismo só, onde cada parte percebe e responde ao ambiente.

Aqui o que aprendi construindo.


Por que Go e não Python

O ecossistema de IA vive em Python. LangChain, LlamaIndex, CrewAI — tudo Python. Se você está prototipando ou rodando notebooks, faz sentido total.

Mas se você está rodando algo em produção com escala de verdade — usuários reais mandando mensagens reais e você precisando de observabilidade, controle de concorrência e algo que não cai às 3 da manhã — Go é uma história completamente diferente.

Go te dá:

  • Goroutines e primitivas de concorrência reais
  • O race detector (go test -race) — que vai encontrar bugs que Python nem vê
  • Performance previsível sem surpresa do GC no pior momento
  • Tipos estáticos que eliminam categorias inteiras de bug em tempo de compilação

Os frameworks Python assumem que você vai ter uma requisição por vez ou vai tratar concorrência via filas fora do framework. Quando você está lidando com webhooks de WhatsApp em escala — múltiplos eventos do mesmo usuário chegando com milissegundos de diferença — essa suposição quebra.


A Arquitetura: Hexagonal, Não "Olha Seu Wrapper de OpenAI"

O princípio central do eywa é que o domínio de negócio não pode ter absolutamente nenhum conhecimento de infraestrutura.

Sem import do SDK da OpenAI no código de domínio. Sem chamadas ao Redis. Sem queries no MongoDB. Só interfaces — o que o eywa chama de ports.

O domínio define o que precisa. A infraestrutura implementa. O wiring acontece na inicialização.

Aqui o port do Bond — o lock distribuído:

type Bond interface {
    AcquireLock(ctx context.Context, key string, ttl time.Duration) (bool, error)
    ReleaseLock(ctx context.Context, key string) error
    ExtendLock(ctx context.Context, key string, ttl time.Duration) error
}
Enter fullscreen mode Exit fullscreen mode

O domínio sabe que pode adquirir e liberar locks. Não sabe que a implementação usa Redis Redlock por baixo. Em testes, você injeta um no-op. Em produção, injeta o adapter Redis.

Mesmo padrão para o Oracle — a abstração de LLM:

type OracleRequest struct {
    Model         string
    SystemPrompt  string
    Messages      []OracleMessage
    Temperature   float64
    MaxTokens     int
    Tools         []OracleTool
    UseTools      bool
    Attachments   []LLMAttachment
}
Enter fullscreen mode Exit fullscreen mode

O domínio manda um OracleRequest. Se vai para Anthropic, OpenAI, Gemini, Bedrock ou VertexAI é detalhe de infraestrutura. Troca provider na inicialização. Roda múltiplos ao mesmo tempo. O domínio não sabe e não precisa saber.

Isso não é over-engineering. É o que torna o sistema testável, manutenível e sobrevivível quando o próximo provider de LLM lançar e todo mundo quiser trocar.


O Léxico: Nomes Que Significam Alguma Coisa

Uma coisa onde investi pesado: nomenclatura. Não só nomes limpos de variável — um vocabulário de domínio consistente que todo pedaço do código usa.

Sim, os nomes são intencionais. Queria um vocabulário de domínio consistente em vez de "Manager", "Service", "Handler" e "Util" — que não dizem nada sobre o que o componente realmente faz no contexto de um agente de IA.

Nome O que é
Weave Motor de runtime — orquestra tudo por evento
Spirit Configuração do agente — LLM, tools, system prompt, comportamento
Pulse Evento de entrada — uma mensagem recebida de um canal
Oracle Abstração de LLM — manda prompt, recebe resposta
Bond Lock distribuído — evita respostas duplicadas concorrentes
Voice Adapter de saída — manda resposta de volta pro canal
Scout Enriquecimento de contexto — roda antes da chamada ao LLM
Lore RAG — geração aumentada por recuperação
Imprint Injeção de memória de longo prazo
Vigil Human-in-the-loop — pausa o agente para resposta humana
Rite Approval workflow — barra ações por trás de confirmação humana
Conduit Adapter cliente MCP (Model Context Protocol)

Quando seu código fala bond.AcquireLock(...) em vez de redisLock.Lock(...), você para de pensar em infraestrutura e começa a pensar no domínio. Terminologia é design.


O Problema Que Ninguém Fala: Pulses Concorrentes

Cenário que acontece em produção e quase nenhum framework trata:

Usuário manda mensagem no WhatsApp. Webhook dispara. Seu agente começa a processar — chamada ao LLM em andamento, 800ms dentro dela.

Usuário fica impaciente e manda a mesma mensagem de novo. Segundo webhook dispara.

Agora você tem duas goroutines processando o contexto do mesmo usuário simultaneamente. A primeira termina, escreve a resposta e atualiza a memória. A segunda termina, escreve outra resposta usando estado de memória stale, sobrescrevendo a primeira atualização.

O usuário recebe duas respostas. A memória está inconsistente. Você criou uma race condition no nível da aplicação.

Isso é o Bond.

Antes do Weave processar qualquer Pulse, ele adquire um lock distribuído com chave no session ID do usuário. Se o lock já está em posse de outra goroutine, o evento é descartado. Só um processamento ativo por usuário, sempre.

O contrato é preciso: AcquireLock retorna (false, nil) quando o lock está ocupado (caso esperado), e (false, error) só para falhas de infraestrutura. Essa distinção importa — o chamador trata os dois de forma diferente.


Scouts: Enriquecimento de Contexto Antes de Toda Chamada ao LLM

O pipeline que roda a cada Pulse antes da chamada ao LLM:

Pulse → Scouts → Pathfinder → Spirit → Oracle → Actions → Voice
Enter fullscreen mode Exit fullscreen mode

Scouts são etapas sequenciais de enriquecimento de contexto. Leem de sistemas externos e injetam conhecimento no Pulse antes do modelo ver qualquer coisa.

type Scout interface {
    GetName() string
    Harvest(ctx context.Context, event *entities.Pulse) error
    IsApplicable(event *entities.Pulse) bool
}
Enter fullscreen mode Exit fullscreen mode

A decisão crítica de design: Scouts são fail-open.

Um Scout que retorna erro é logado. O pipeline continua sem os dados dele. A chamada ao LLM acontece assim mesmo.

Por quê? Porque se um Scout está batendo num CRM para enriquecer o contexto do usuário, e o CRM está lento naquela manhã, você não quer que seu agente inteiro pare de responder. Você quer que continue funcionando com menos contexto, graciosamente.


Wiring: Como Tudo Se Conecta

O Weave inteiro é montado na inicialização com um builder fluente:

weave, err := eywa.NewWeaveBuilder(ctx).
    WithRepositories(spiritRepo, memoryRepo, echoRepo, chronicleRepo).
    WithBond(bond).
    WithActionRegistry(eywa.NewActionRegistry()).
    WithScoutRegistry(eywa.NewScoutRegistry()).
    AddOracle(eywaopenai.NewOracle(apiKey)).
    WithConfig(config).
    Build()
Enter fullscreen mode Exit fullscreen mode

MongoDB para configuração de Spirits e histórico de conversa. Redis para lock distribuído e memória em andamento. OpenAI como Oracle. Tudo injetado — nada global.

Para adicionar Anthropic como provider adicional:

AddOracle(eywaopenai.NewOracle(openaiKey)).
AddOracle(eywaanthropic.NewOracle(anthropicKey)).
Enter fullscreen mode Exit fullscreen mode

Spirits definem qual provider usam. O OracleFactory seleciona o correto em runtime.


19 Módulos: Paga Só o Que Usa

O framework inteiro é distribuído como 19 módulos Go independentes:

github.com/wmulabs/eywa                     # core
github.com/wmulabs/eywa/fiber               # adapter HTTP
github.com/wmulabs/eywa/mongo               # repositórios MongoDB
github.com/wmulabs/eywa/redis               # Bond + memória Redis
github.com/wmulabs/eywa/mcp                 # cliente MCP (Conduit)
github.com/wmulabs/eywa/providers/anthropic
github.com/wmulabs/eywa/providers/openai
github.com/wmulabs/eywa/providers/gemini
github.com/wmulabs/eywa/providers/bedrock
github.com/wmulabs/eywa/providers/vertexai
github.com/wmulabs/eywa/providers/weaviate
github.com/wmulabs/eywa/providers/qdrant
github.com/wmulabs/eywa/providers/pgvector
github.com/wmulabs/eywa/providers/pinecone
github.com/wmulabs/eywa/channels/whatsapp
github.com/wmulabs/eywa/gcp/cloudtasks
github.com/wmulabs/eywa/gcp/gcs
github.com/wmulabs/eywa/gcp/gemini
Enter fullscreen mode Exit fullscreen mode

Se você não usa Bedrock, não importa. Não recebe as dependências dele no go.sum. Não recebe a superfície de segurança dele. Desenvolvedor Go liga pra isso.


Segurança Não Foi Deixada Pra Depois

Antes de chamar isso de v1.0, fiz uma revisão de segurança séria:

  • SSRF bloqueado em todo cliente HTTP de saída — IPs privados, loopback, IMDS (169.254.169.254), file://, ftp://
  • io.LimitReader em toda resposta HTTP — um servidor malicioso não consegue dar OOM no seu agente
  • API keys como SHA-256 com subtle.ConstantTimeCompare — sem timing attacks
  • Rate limiting nos endpoints de auth
  • go test -race em todos os 19 módulos em cada push de CI, sem exceção

Nada disso é animador. Tudo importa.


O Que Vem a Seguir

eywa está em v1.0.0. Estável, hardened, documentado.

Se você está construindo agentes de IA em Go — ou quer construir mas não achava nada sério o suficiente pra basear um sistema de produção — adoraria seu feedback.

Pull requests bem-vindos. Issues bem-vindas. Crítica direta bem-vinda.

github.com/wmulabs/eywa


Talvez o mundo não precisasse de mais um framework de IA. Mas definitivamente precisava de mais engenharia nele.

Top comments (0)