<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Willian R Moraes</title>
    <description>The latest articles on DEV Community by Willian R Moraes (@wmoraes).</description>
    <link>https://dev.to/wmoraes</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3957729%2F63f733ad-a52b-457e-b88f-c17c4b81fafb.jpg</url>
      <title>DEV Community: Willian R Moraes</title>
      <link>https://dev.to/wmoraes</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/wmoraes"/>
    <language>en</language>
    <item>
      <title>Não achei um framework Go production-ready para agentes de IA. Então construí um.</title>
      <dc:creator>Willian R Moraes</dc:creator>
      <pubDate>Fri, 29 May 2026 05:13:54 +0000</pubDate>
      <link>https://dev.to/wmoraes/-nao-achei-um-framework-go-production-ready-para-agentes-de-ia-entao-construi-um-1d6a</link>
      <guid>https://dev.to/wmoraes/-nao-achei-um-framework-go-production-ready-para-agentes-de-ia-entao-construi-um-1d6a</guid>
      <description>&lt;p&gt;&lt;em&gt;Como construí um framework Go para agentes de IA em produção — e as decisões de arquitetura que realmente importam.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Quinze anos escrevendo software profissionalmente e nunca open sourcei uma linha de código.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Dois meses atrás decidi mudar isso.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Nada production-ready. Nada com arquitetura de verdade. Nada que eu pudesse passar para um time e falar &lt;em&gt;isso aguenta&lt;/em&gt;.&lt;/p&gt;

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

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Aqui o que aprendi construindo.&lt;/p&gt;




&lt;h2&gt;
  
  
  Por que Go e não Python
&lt;/h2&gt;

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

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Go te dá:&lt;/p&gt;

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

&lt;p&gt;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.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Arquitetura: Hexagonal, Não "Olha Seu Wrapper de OpenAI"
&lt;/h2&gt;

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

&lt;p&gt;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 &lt;strong&gt;ports&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;O domínio define o que precisa. A infraestrutura implementa. O wiring acontece na inicialização.&lt;/p&gt;

&lt;p&gt;Aqui o port do Bond — o lock distribuído:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Bond&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;AcquireLock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ttl&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ReleaseLock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;
    &lt;span class="n"&gt;ExtendLock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ttl&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Mesmo padrão para o Oracle — a abstração de LLM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;OracleRequest&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Model&lt;/span&gt;         &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;SystemPrompt&lt;/span&gt;  &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;Messages&lt;/span&gt;      &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;OracleMessage&lt;/span&gt;
    &lt;span class="n"&gt;Temperature&lt;/span&gt;   &lt;span class="kt"&gt;float64&lt;/span&gt;
    &lt;span class="n"&gt;MaxTokens&lt;/span&gt;     &lt;span class="kt"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;Tools&lt;/span&gt;         &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;OracleTool&lt;/span&gt;
    &lt;span class="n"&gt;UseTools&lt;/span&gt;      &lt;span class="kt"&gt;bool&lt;/span&gt;
    &lt;span class="n"&gt;Attachments&lt;/span&gt;   &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;LLMAttachment&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O domínio manda um &lt;code&gt;OracleRequest&lt;/code&gt;. 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;




&lt;h2&gt;
  
  
  O Léxico: Nomes Que Significam Alguma Coisa
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

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

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




&lt;h2&gt;
  
  
  O Problema Que Ninguém Fala: Pulses Concorrentes
&lt;/h2&gt;

&lt;p&gt;Cenário que acontece em produção e quase nenhum framework trata:&lt;/p&gt;

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

&lt;p&gt;Usuário fica impaciente e manda a mesma mensagem de novo. Segundo webhook dispara.&lt;/p&gt;

&lt;p&gt;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 &lt;em&gt;outra&lt;/em&gt; resposta usando estado de memória stale, sobrescrevendo a primeira atualização.&lt;/p&gt;

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

&lt;p&gt;Isso é o Bond.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

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




&lt;h2&gt;
  
  
  Scouts: Enriquecimento de Contexto Antes de Toda Chamada ao LLM
&lt;/h2&gt;

&lt;p&gt;O pipeline que roda a cada Pulse antes da chamada ao LLM:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pulse → Scouts → Pathfinder → Spirit → Oracle → Actions → Voice
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Scouts são etapas sequenciais de enriquecimento de contexto. Leem de sistemas externos e injetam conhecimento no Pulse antes do modelo ver qualquer coisa.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Scout&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;GetName&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;Harvest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;entities&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pulse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;
    &lt;span class="n"&gt;IsApplicable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;entities&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pulse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A decisão crítica de design: &lt;strong&gt;Scouts são fail-open&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Um Scout que retorna erro é logado. O pipeline continua sem os dados dele. A chamada ao LLM acontece assim mesmo.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wiring: Como Tudo Se Conecta
&lt;/h2&gt;

&lt;p&gt;O Weave inteiro é montado na inicialização com um builder fluente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;weave&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;eywa&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewWeaveBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;WithRepositories&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spiritRepo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;memoryRepo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;echoRepo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chronicleRepo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;WithBond&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bond&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;WithActionRegistry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;eywa&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewActionRegistry&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;WithScoutRegistry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;eywa&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewScoutRegistry&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;AddOracle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;eywaopenai&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewOracle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;WithConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Para adicionar Anthropic como provider adicional:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;AddOracle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;eywaopenai&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewOracle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;openaiKey&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;AddOracle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;eywaanthropic&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewOracle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;anthropicKey&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spirits definem qual provider usam. O OracleFactory seleciona o correto em runtime.&lt;/p&gt;




&lt;h2&gt;
  
  
  19 Módulos: Paga Só o Que Usa
&lt;/h2&gt;

&lt;p&gt;O framework inteiro é distribuído como 19 módulos Go independentes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="err"&gt;github.com/wmulabs/eywa&lt;/span&gt;                     &lt;span class="c"&gt;# core
&lt;/span&gt;&lt;span class="err"&gt;github.com/wmulabs/eywa/fiber&lt;/span&gt;               &lt;span class="c"&gt;# adapter HTTP
&lt;/span&gt;&lt;span class="err"&gt;github.com/wmulabs/eywa/mongo&lt;/span&gt;               &lt;span class="c"&gt;# repositórios MongoDB
&lt;/span&gt;&lt;span class="err"&gt;github.com/wmulabs/eywa/redis&lt;/span&gt;               &lt;span class="c"&gt;# Bond + memória Redis
&lt;/span&gt;&lt;span class="err"&gt;github.com/wmulabs/eywa/mcp&lt;/span&gt;                 &lt;span class="c"&gt;# cliente MCP (Conduit)
&lt;/span&gt;&lt;span class="err"&gt;github.com/wmulabs/eywa/providers/anthropic&lt;/span&gt;
&lt;span class="err"&gt;github.com/wmulabs/eywa/providers/openai&lt;/span&gt;
&lt;span class="err"&gt;github.com/wmulabs/eywa/providers/gemini&lt;/span&gt;
&lt;span class="err"&gt;github.com/wmulabs/eywa/providers/bedrock&lt;/span&gt;
&lt;span class="err"&gt;github.com/wmulabs/eywa/providers/vertexai&lt;/span&gt;
&lt;span class="err"&gt;github.com/wmulabs/eywa/providers/weaviate&lt;/span&gt;
&lt;span class="err"&gt;github.com/wmulabs/eywa/providers/qdrant&lt;/span&gt;
&lt;span class="err"&gt;github.com/wmulabs/eywa/providers/pgvector&lt;/span&gt;
&lt;span class="err"&gt;github.com/wmulabs/eywa/providers/pinecone&lt;/span&gt;
&lt;span class="err"&gt;github.com/wmulabs/eywa/channels/whatsapp&lt;/span&gt;
&lt;span class="err"&gt;github.com/wmulabs/eywa/gcp/cloudtasks&lt;/span&gt;
&lt;span class="err"&gt;github.com/wmulabs/eywa/gcp/gcs&lt;/span&gt;
&lt;span class="err"&gt;github.com/wmulabs/eywa/gcp/gemini&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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




&lt;h2&gt;
  
  
  Segurança Não Foi Deixada Pra Depois
&lt;/h2&gt;

&lt;p&gt;Antes de chamar isso de v1.0, fiz uma revisão de segurança séria:&lt;/p&gt;

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

&lt;p&gt;Nada disso é animador. Tudo importa.&lt;/p&gt;




&lt;h2&gt;
  
  
  O Que Vem a Seguir
&lt;/h2&gt;

&lt;p&gt;eywa está em v1.0.0. Estável, hardened, documentado.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Pull requests bem-vindos. Issues bem-vindas. Crítica direta bem-vinda.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;→ &lt;a href="https://github.com/wmulabs/eywa" rel="noopener noreferrer"&gt;github.com/wmulabs/eywa&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Talvez o mundo não precisasse de mais um framework de IA. Mas definitivamente precisava de mais engenharia nele.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>go</category>
      <category>ai</category>
      <category>architecture</category>
    </item>
    <item>
      <title>I Couldn't Find a Production-Ready Go Framework for AI Agents. So I Built One.</title>
      <dc:creator>Willian R Moraes</dc:creator>
      <pubDate>Fri, 29 May 2026 05:03:27 +0000</pubDate>
      <link>https://dev.to/wmoraes/-i-couldnt-find-a-production-ready-go-framework-for-ai-agents-so-i-built-one-3je8</link>
      <guid>https://dev.to/wmoraes/-i-couldnt-find-a-production-ready-go-framework-for-ai-agents-so-i-built-one-3je8</guid>
      <description>&lt;p&gt;&lt;em&gt;How I built a production-grade Go framework for conversational AI agents — and the architecture decisions that actually matter.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;Fifteen years of writing software professionally and I never open sourced a single thing.&lt;/p&gt;

&lt;p&gt;Not because I didn't want to. It's just how it works when you build inside companies — the code belongs to them, the problems are specific to their domain, and by the time you could abstract something useful, you've already moved on to the next fire.&lt;/p&gt;

&lt;p&gt;Two months ago I decided to change that.&lt;/p&gt;

&lt;p&gt;I was building a conversational AI agent in Go. Needed a framework. Went looking for one and found... Python. More Python. A few Go repos that were abandoned in 2023. And a lot of "just wrap the OpenAI SDK" advice that works fine until you have real traffic and your agent starts responding twice to the same message.&lt;/p&gt;

&lt;p&gt;Nothing production-ready. Nothing with actual architecture. Nothing I could hand to a team and say &lt;em&gt;this will hold up.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;So I built it. The result is &lt;strong&gt;eywa&lt;/strong&gt; — a Go framework for conversational AI agents, hexagonal architecture, v1.0.0, MIT license, open source.&lt;/p&gt;

&lt;p&gt;The name comes from Avatar — Eywa is the neural network connecting all living things on Pandora. The metaphor fit: a system that connects LLMs, channels, memory, and tools into a single organism, where each part perceives and responds to the environment.&lt;/p&gt;

&lt;p&gt;Here's what I learned building it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why Go, not Python
&lt;/h2&gt;

&lt;p&gt;The AI ecosystem lives in Python. LangChain, LlamaIndex, CrewAI — all Python. If you're prototyping, exploring, or running notebooks, this makes complete sense.&lt;/p&gt;

&lt;p&gt;But if you're running something in production at scale — where real users are sending real messages and you need observability, concurrency control, and something that doesn't fall over at 3am — Go is a very different story.&lt;/p&gt;

&lt;p&gt;Go gives you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Goroutines and real concurrency primitives&lt;/li&gt;
&lt;li&gt;The race detector (&lt;code&gt;go test -race&lt;/code&gt;) — which will find bugs Python won't even see&lt;/li&gt;
&lt;li&gt;Predictable performance without a GC surprise at the worst moment&lt;/li&gt;
&lt;li&gt;Static types that catch entire categories of bugs at compile time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Python frameworks assume you'll have one request at a time or handle concurrency via queues outside the framework. When you're dealing with WhatsApp webhooks at scale — multiple events per user arriving milliseconds apart — that assumption breaks.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Architecture: Hexagonal, Not "Here's Your OpenAI Wrapper"
&lt;/h2&gt;

&lt;p&gt;The core principle in eywa is that the business domain should have absolutely zero knowledge of infrastructure.&lt;/p&gt;

&lt;p&gt;No OpenAI SDK imports in domain code. No Redis calls. No MongoDB queries. Just interfaces — what eywa calls &lt;strong&gt;ports&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The domain defines what it needs. Infrastructure implements it. Wiring happens at startup.&lt;/p&gt;

&lt;p&gt;Here's the Bond port — the distributed lock:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Bond&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;AcquireLock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ttl&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ReleaseLock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;
    &lt;span class="n"&gt;ExtendLock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ttl&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The domain knows it can acquire and release locks. It does not know that the implementation uses Redis Redlock under the hood. In tests, you inject a no-op. In production, you inject the Redis adapter.&lt;/p&gt;

&lt;p&gt;Same pattern for the Oracle (the LLM abstraction):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;OracleRequest&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Model&lt;/span&gt;         &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;SystemPrompt&lt;/span&gt;  &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;Messages&lt;/span&gt;      &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;OracleMessage&lt;/span&gt;
    &lt;span class="n"&gt;Temperature&lt;/span&gt;   &lt;span class="kt"&gt;float64&lt;/span&gt;
    &lt;span class="n"&gt;MaxTokens&lt;/span&gt;     &lt;span class="kt"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;Tools&lt;/span&gt;         &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;OracleTool&lt;/span&gt;
    &lt;span class="n"&gt;UseTools&lt;/span&gt;      &lt;span class="kt"&gt;bool&lt;/span&gt;
    &lt;span class="n"&gt;Attachments&lt;/span&gt;   &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="n"&gt;LLMAttachment&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The domain sends an &lt;code&gt;OracleRequest&lt;/code&gt;. Whether that goes to Anthropic, OpenAI, Gemini, Bedrock, or VertexAI is an infrastructure concern. Swap providers at startup. Run multiple providers simultaneously. The domain doesn't care.&lt;/p&gt;

&lt;p&gt;This is not over-engineering. It's what makes the system testable, maintainable, and survivable when the next LLM provider comes out and everyone wants to switch.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Lexicon: Names That Actually Mean Something
&lt;/h2&gt;

&lt;p&gt;One thing I invested heavily in: naming. Not just clean variable names — a consistent domain vocabulary that every piece of code uses.&lt;/p&gt;

&lt;p&gt;Yes, the names are intentional. I wanted a consistent domain vocabulary instead of "Manager", "Service", "Handler", and "Util" — names that tell you nothing about what the component actually does in the context of an AI agent.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Weave&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The runtime engine — orchestrates everything per event&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Spirit&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Agent configuration — LLM, tools, system prompt, behavior&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Pulse&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Inbound event — a message received from a channel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Oracle&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;LLM abstraction — send prompt, receive response&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Bond&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Distributed lock — prevents concurrent duplicate responses&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Voice&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Outbound adapter — sends replies back to the channel&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scout&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Context enrichment step — runs before the LLM call&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Lore&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;RAG — retrieval-augmented generation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Imprint&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Long-term memory injection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Vigil&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Human-in-the-loop takeover&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Rite&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Approval workflow — gates actions behind human confirmation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Conduit&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;MCP (Model Context Protocol) client adapter&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When your code says &lt;code&gt;bond.AcquireLock(...)&lt;/code&gt; instead of &lt;code&gt;redisLock.Lock(...)&lt;/code&gt;, you stop thinking about infrastructure and start thinking about the domain. Terminology is design.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem Nobody Talks About: Concurrent Pulses
&lt;/h2&gt;

&lt;p&gt;Here's a scenario that happens in production and almost no framework handles it:&lt;/p&gt;

&lt;p&gt;A user sends a WhatsApp message. The webhook fires. Your agent starts processing — LLM call in progress, 800ms into it.&lt;/p&gt;

&lt;p&gt;The user gets impatient and sends the same message again. Second webhook fires.&lt;/p&gt;

&lt;p&gt;Now you have two goroutines processing the same user's context simultaneously. The first finishes, writes the response and updates memory. The second finishes, writes &lt;em&gt;another&lt;/em&gt; response using stale memory state, overwriting the first update.&lt;/p&gt;

&lt;p&gt;The user gets two responses. Memory is inconsistent. You've introduced a race condition at the application level.&lt;/p&gt;

&lt;p&gt;This is Bond.&lt;/p&gt;

&lt;p&gt;Before the Weave processes any Pulse, it acquires a distributed lock keyed by the user's session ID. If the lock is already held, the event is discarded. Only one active processing per user, ever.&lt;/p&gt;

&lt;p&gt;The contract is precise: &lt;code&gt;AcquireLock&lt;/code&gt; returns &lt;code&gt;(false, nil)&lt;/code&gt; when the lock is held (expected case), and &lt;code&gt;(false, error)&lt;/code&gt; only for infrastructure failures. This distinction matters — the caller handles them differently.&lt;/p&gt;




&lt;h2&gt;
  
  
  Scouts: Context Enrichment Before Every LLM Call
&lt;/h2&gt;

&lt;p&gt;The pipeline that runs on every Pulse before the LLM call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Pulse → Scouts → Pathfinder → Spirit → Oracle → Actions → Voice
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Scouts are sequential context enrichment steps. They read from external systems and inject knowledge into the Pulse before the model sees anything.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Scout&lt;/span&gt; &lt;span class="k"&gt;interface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;GetName&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;Harvest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;entities&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pulse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;
    &lt;span class="n"&gt;IsApplicable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;entities&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pulse&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The critical design decision: &lt;strong&gt;Scouts are fail-open&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A Scout that returns an error gets logged. The pipeline continues without its data. The LLM call still happens.&lt;/p&gt;

&lt;p&gt;Why? Because if a Scout is hitting a CRM to enrich the user's context, and the CRM is having a slow morning, you don't want your entire agent to stop responding. You want it to keep working with less context, gracefully.&lt;/p&gt;




&lt;h2&gt;
  
  
  Wiring It All Together
&lt;/h2&gt;

&lt;p&gt;The entire Weave is assembled at startup with a fluent builder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;weave&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;eywa&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewWeaveBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;WithRepositories&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spiritRepo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;memoryRepo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;echoRepo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chronicleRepo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;WithBond&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bond&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;WithActionRegistry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;eywa&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewActionRegistry&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;WithScoutRegistry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;eywa&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewScoutRegistry&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;AddOracle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;eywaopenai&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewOracle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;WithConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
    &lt;span class="n"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MongoDB for Spirit configuration and conversation history. Redis for distributed locking and in-flight memory. OpenAI as the Oracle. Everything injected — nothing global.&lt;/p&gt;

&lt;p&gt;To add Anthropic as an additional provider:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;AddOracle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;eywaopenai&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewOracle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;openaiKey&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;span class="n"&gt;AddOracle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;eywaanthropic&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewOracle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;anthropicKey&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Spirits define which provider they use. The OracleFactory selects the right one at runtime.&lt;/p&gt;




&lt;h2&gt;
  
  
  19 Modules: Pay for What You Use
&lt;/h2&gt;

&lt;p&gt;The entire framework ships as 19 independent Go modules:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="err"&gt;github.com/wmulabs/eywa&lt;/span&gt;                     &lt;span class="c"&gt;# core
&lt;/span&gt;&lt;span class="err"&gt;github.com/wmulabs/eywa/fiber&lt;/span&gt;               &lt;span class="c"&gt;# HTTP adapter
&lt;/span&gt;&lt;span class="err"&gt;github.com/wmulabs/eywa/mongo&lt;/span&gt;               &lt;span class="c"&gt;# MongoDB repositories
&lt;/span&gt;&lt;span class="err"&gt;github.com/wmulabs/eywa/redis&lt;/span&gt;               &lt;span class="c"&gt;# Redis Bond + memory
&lt;/span&gt;&lt;span class="err"&gt;github.com/wmulabs/eywa/mcp&lt;/span&gt;                 &lt;span class="c"&gt;# MCP client (Conduit)
&lt;/span&gt;&lt;span class="err"&gt;github.com/wmulabs/eywa/providers/anthropic&lt;/span&gt;
&lt;span class="err"&gt;github.com/wmulabs/eywa/providers/openai&lt;/span&gt;
&lt;span class="err"&gt;github.com/wmulabs/eywa/providers/gemini&lt;/span&gt;
&lt;span class="err"&gt;github.com/wmulabs/eywa/providers/bedrock&lt;/span&gt;
&lt;span class="err"&gt;github.com/wmulabs/eywa/providers/vertexai&lt;/span&gt;
&lt;span class="err"&gt;github.com/wmulabs/eywa/providers/weaviate&lt;/span&gt;
&lt;span class="err"&gt;github.com/wmulabs/eywa/providers/qdrant&lt;/span&gt;
&lt;span class="err"&gt;github.com/wmulabs/eywa/providers/pgvector&lt;/span&gt;
&lt;span class="err"&gt;github.com/wmulabs/eywa/providers/pinecone&lt;/span&gt;
&lt;span class="err"&gt;github.com/wmulabs/eywa/channels/whatsapp&lt;/span&gt;
&lt;span class="err"&gt;github.com/wmulabs/eywa/gcp/cloudtasks&lt;/span&gt;
&lt;span class="err"&gt;github.com/wmulabs/eywa/gcp/gcs&lt;/span&gt;
&lt;span class="err"&gt;github.com/wmulabs/eywa/gcp/gemini&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you don't use Bedrock, you don't import it. You don't get its dependencies in your &lt;code&gt;go.sum&lt;/code&gt;. You don't get its security surface. Go developers care about this.&lt;/p&gt;




&lt;h2&gt;
  
  
  Security Was Not an Afterthought
&lt;/h2&gt;

&lt;p&gt;Before calling this v1.0, I went through a proper security review:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;SSRF protection&lt;/strong&gt; on all outbound HTTP — private IPs, loopback, IMDS (169.254.169.254), &lt;code&gt;file://&lt;/code&gt;, &lt;code&gt;ftp://&lt;/code&gt; all blocked&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;io.LimitReader&lt;/code&gt;&lt;/strong&gt; on every HTTP response body — a misbehaving server can't OOM your agent&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SHA-256 API key storage&lt;/strong&gt; with &lt;code&gt;subtle.ConstantTimeCompare&lt;/code&gt; — no timing attacks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rate limiting&lt;/strong&gt; on auth endpoints&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;go test -race&lt;/code&gt;&lt;/strong&gt; across all 19 modules on every CI push, no exceptions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is exciting. All of it matters.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;eywa is at v1.0.0. Stable, production-hardened, documented.&lt;/p&gt;

&lt;p&gt;If you're building AI agents in Go — or you've been wanting to but couldn't find something serious enough to base a production system on — I'd genuinely love your feedback.&lt;/p&gt;

&lt;p&gt;Pull requests welcome. Issues welcome. Blunt criticism welcome.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;→ &lt;a href="https://github.com/wmulabs/eywa" rel="noopener noreferrer"&gt;github.com/wmulabs/eywa&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Maybe the world didn't need another AI framework. But it definitely needed more engineering in the ones it had.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>go</category>
      <category>ai</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
