# danicat.dev — Daniela Petruzalek (Full Corpus) > Tecnologista, gamer e gateira — Engenheira Sênior de Developer Relations no Google > For the compact index, see [llms.txt](https://danicat.dev/pt-br/llms.txt). - Website: https://danicat.dev/pt-br/ - Compact Index (llms.txt): https://danicat.dev/pt-br/llms.txt - Full Corpus (llms-full.txt): https://danicat.dev/pt-br/llms-full.txt - Language Editions: [English (llms-full.txt)](https://danicat.dev/llms-full.txt) | [Português (llms-full.txt)](https://danicat.dev/pt-br/llms-full.txt) | [日本語 (llms-full.txt)](https://danicat.dev/ja/llms-full.txt) - Agent Skills Catalog: https://skills.danicat.dev/ (LLMs index: https://skills.danicat.dev/llms.txt | JSON manifest: https://skills.danicat.dev/catalog.json) --- # Além de Escrever Código: Bom Gosto, Skills e Tech Leads - **Markdown URL**: https://danicat.dev/pt-br/posts/beyond-writing-code/index.md - **HTML URL**: https://danicat.dev/pt-br/posts/beyond-writing-code/ - **Date**: 2026-10-28 - **Categories**: Perspectives - **Tags**: agent-skills, antigravity, career, code-review, subagents, vibe-coding - **Summary**: Por que a engenharia de software na era dos agentes de código migra da sintaxe para o bom gosto arquitetural, subagentes, agent skills e a postura de tech lead. Quando os modelos de fundação trazem todo o conhecimento do mundo embutido em seus pesos, o que sobra para os humanos? Por décadas, nossa identidade como engenheiros de software esteve atrelada à arte de escrever código: dominar sintaxes, memorizar bibliotecas padrão e nos orgulhar dos sistemas que construíamos com nossas próprias mãos. Disseram-nos que a IA simplesmente cuidaria do boilerplate chato e nos devolveria horas livres toda semana. Em vez disso, ver um modelo gerar em segundos o que levamos anos para aprender desencadeou uma verdadeira crise de identidade no setor. Se a máquina pode escrever o código, qual é o nosso lugar? Os humanos devem ser o farol. Justamente porque os modelos sabem *tudo*, qualquer caminho parece igualmente plausível. Eles não conseguem priorizar e encadear informações para criar com **bom gosto**. É aqui que entra o nosso novo trabalho: como se estivéssemos explorando uma caverna escura, nós seguramos a lanterna, apontando para o caminho e decidindo onde o modelo deve focar a seguir. Nosso papel evoluiu da escrita de código para a orquestração. Para muitos de nós, isso não é novidade, mesmo antes da codificação com IA: é a progressão natural da carreira de engenharia de software. Conforme você sobe na carreira, você põe menos a mão na massa e veste o chapéu de mentor cada vez mais. Você se torna responsável pelas entregas da sua equipe, para de ler cada linha de código para focar nas partes críticas, aprende a delegar e a lidar com as vantagens e desvantagens dessa escolha. Você se torna um tech lead. ## Todo mundo foi promovido a tech lead ![Em 2026 Todo Engenheiro Foi Promovido a Tech Lead](everyone-is-a-tech-lead.png "Parabéns pela sua promoção a Tech Lead. (P.S.: O RH confirmou que sua faixa salarial permanece inalterada.)") Na trajetória tradicional de carreira, essa transição acontecia de forma gradual. Você passava anos nas trincheiras evoluindo de Júnior para Sênior antes de finalmente assumir o papel de lead, engineering manager ou arquiteto, focando na visão macro enquanto engenheiros mais juniores ficavam com os detalhes de implementação. Agora, todo mundo tem sua equipe virtual de agentes de código. Portanto, até o engenheiro mais júnior precisa aprender a se tornar um bom tech lead. Não para liderar humanos, mas para guiar os agentes. "Guiar para fazer o quê?" é a pergunta mais importante. Ainda estamos construindo programas, mas a forma como os descrevemos mudou. Existem poucos livros que li na vida que considero verdadeiramente atemporais. Um deles é *Structure and Interpretation of Computer Programs* (aka SICP). Por que esse livro é relevante agora? Pela maneira como ele descreve programas e como nós, engenheiros, "conjuramos feitiços" para construí-los: > *"Os processos computacionais são seres abstratos que habitam os computadores. À medida que evoluem, os processos manipulam outras coisas abstratas chamadas **dados**. A evolução de um processo é dirigida por um padrão de regras chamado **programa**. As pessoas criam programas para dirigir processos. Na prática, conjuramos os espíritos do computador com nossos feitiços."* > > *"... Os programas que usamos para conjurar processos são como os feitiços de um feiticeiro. Eles são cuidadosamente compostos a partir de expressões simbólicas em linguagens de programação arcanas e esotéricas que prescrevem as tarefas que queremos que nossos processos executem."* > > — Harold Abelson e Gerald Jay Sussman, *Structure and Interpretation of Computer Programs* (1985) Ainda estamos no negócio de conjurar processos para instruir os computadores. Apenas a linguagem na qual criamos nossos feitiços mudou. Agora que escrevemos esses feitiços em linguagem natural em vez de expressões simbólicas arcanas, orquestrar agentes e garantir que eles estejam no caminho certo é a parte mais importante do trabalho. Isso implica escrever menos código e ler muito mais. Você também escreve mais especificações (prompts) do que nunca. Se você já foi tech lead antes da IA, tudo isso vai soar como o dia a dia de sempre. Como ficar bom nisso? No mundo antigo, os bons tech leads e gerentes passavam muito tempo treinando e mentorando suas equipes para que desenvolvessem o mesmo pensamento crítico e bom gosto adquiridos ao longo de suas carreiras. Eles transferiam gradualmente o conhecimento de processos, comportamentos e técnicas, muitas vezes desenvolvidos à base de muitas cicatrizes de batalha. Aquele code review detalhista que acabei de fazer? Foi porque minha própria falta de atenção aos detalhes me fez derrubar a produção muitos anos atrás. No mundo de hoje, além de aprender com nossos mentores e nossas próprias cicatrizes, precisamos observar atentamente o comportamento dos nossos agentes e aprender com os erros **deles**. Como esses erros se acumulam em velocidade vertiginosa, os agentes oferecem o ciclo de feedback comprimido definitivo: você assiste a dezenas de modos sutis de falha, condições de corrida e padrões alucinados se desenrolarem em uma única tarde, construindo discernimento arquitetural a dez vezes a velocidade dos ciclos tradicionais de lançamento. Em essência, agora trabalhamos orquestrando o fluxo de informações, garantindo que os agentes sempre tenham o ambiente ideal para construir os resultados que desejamos. Dada a taxa de evolução que estamos vendo, o que é ideal hoje muito provavelmente não será ideal amanhã. Portanto, precisamos refinar constantemente nossas ferramentas e processos para acompanhar o ritmo dessa evolução. Mais do que nunca, software é descartável. Nossos feitiços precisam se tornar rápidos e precisos, pois amanhã precisaremos de novos. ## Estruturando a informação para que os modelos possam agir > *"Engenheiros de software mestres têm a capacidade de organizar programas para que possam ter certeza razoável de que os processos resultantes executarão as tarefas pretendidas. Eles conseguem visualizar o comportamento de seus sistemas com antecedência. Sabem estruturar programas para que problemas imprevistos não levem a consequências catastróficas e, quando surgem problemas, conseguem **depurar** seus programas. Sistemas computacionais bem projetados, assim como automóveis ou reatores nucleares bem projetados, são concebidos de forma modular, para que as peças possam ser construídas, substituídas e depuradas separadamente."* > > — Harold Abelson e Gerald Jay Sussman, *Structure and Interpretation of Computer Programs* (1985) Se você pegar a citação acima e substituir a palavra "programas" por "agentes", todo o raciocínio continua válido. Precisamos aprender a estruturar nossos processos de codificação agêntica para que produzam sistemas computacionais bem projetados. Uma das maneiras mais importantes de fornecer conhecimento a um agente é por meio de [Agent Skills](https://danicat.dev/pt-br/posts/20260829-the-pragmatic-guide-to-agent-skills/). Como os modelos se comportam de maneira muito semelhante aos humanos (o que não deveria surpreender, já que foram treinados com dados que nós produzimos), as mesmas técnicas que aplicávamos para aumentar a produtividade e a qualidade das entregas de nossas equipes são aplicáveis aos agentes. Isso cria um terreno fértil para a criação de skills que influenciarão o comportamento do modelo, inspiradas em suas próprias experiências e cicatrizes de batalha, bem como nas melhores práticas da indústria. Uma dessas técnicas é quebrar tarefas complexas em partes menores. Um dos pilares do processo Ágil, que agora pode ser aplicado à codificação agêntica por meio de spec-driven development, planos de implementação e [subagentes](https://danicat.dev/pt-br/posts/20260722-the-rise-of-the-subagents/). Pessoalmente, trato subagentes como os blocos fundamentais do fluxo de informação que estou desenhando. Isso é, em muitos aspectos, semelhante a lidar com concorrência em programas tradicionais, mas também desempenha um papel crucial no isolamento de contexto. Afinal, "atenção é tudo de que *eles* precisam". Ao quebrar tarefas em unidades ortogonais que podem ser processadas individualmente pelos subagentes, você garante que a atenção preciosa do agente fique focada exclusivamente naquela tarefa única, melhorando a qualidade e a aderência aos requisitos. Sempre que você estiver criando fluxos de trabalho manuais ou construindo skills para a posteridade, considero estes pontos particularmente úteis: 1. **Papel de coordenador**: É útil manter a sessão principal livre garantindo que o coordenador sempre delegue a execução para subagentes, liberando a sessão principal o mais rápido possível. Dessa forma, você pode continuar interagindo com ela enquanto os subagentes trabalham, enviando comentários para orientá-los na direção certa em tempo real. 2. **Ortogonalidade de tarefas**: Divida tarefas em unidades ortogonais para minimizar dependências ou conflitos entre si. Primeiro, entenda e respeite dependências sequenciais. Depois, você pode paralelizar com a confiança de que os subagentes não sobrescreverão o trabalho uns dos outros nem tomarão premissas incompatíveis. 3. **Recriando fluxos comprovados**: Assim como no coaching e na mentoria, padrões que funcionam para equipes humanas e para a engenharia de software clássica também funcionam para agentes. Revisite constantemente a literatura em busca de padrões antigos e novos que você possa ensinar aos agentes. Tenho construído gradualmente meu [catálogo de skills](https://skills.danicat.dev) ao longo dos últimos meses. Como as skills *apodrecem* rápido à medida que modelos e harnesses de agentes evoluem, é importante notar que isso não é algo do tipo "configure e esqueça". Eu as evoluo constantemente, a ponto de decidir adicionar um "freshness score" às minhas skills para me lembrar quais estão se tornando obsoletas, para que eu possa tentar dar uma vida nova a elas ou removê-las do catálogo. Aqui estão alguns exemplos de skills que utilizo ativamente no meu dia a dia: - **`double-diamond`**: fluxo de desenvolvimento de produto baseado na metodologia do duplo diamante das práticas ágeis, forçando a fase de descoberta antes da implementação. - **`parallel`**: padrão multiúso de fan-out / map-reduce que quebra uma tarefa em várias threads e reconcilia os resultados no final. - **`uno-reverse`**: revisão adversarial para combater o excesso de confiança do modelo e a tendência a complicar a arquitetura. - **`less-is-more`**: filosofia de design que instrui o modelo a fazer uma coisa de cada vez, e bem feita, em vez de criar aplicações inchadas. - **`intercom`**: ensina meus agentes a conversarem entre si quando estão trabalhando em projetos correlatos. - **`show-me-the-money`**: minera o histórico da conversa em busca de funcionalidades que discuti com o modelo, mas esqueci completamente de implementar. - **`abstract`**: generaliza exemplos e circunstâncias em conhecimento estruturado usando princípios filosóficos. ## Janelas quebradas no código agêntico > *"Um processo computacional, em um computador operando corretamente, executa programas de forma precisa e acurada. Assim, como o aprendiz de feiticeiro, os programadores novatos precisam aprender a entender e a antecipar as consequências de sua conjuração. Mesmo pequenos erros (geralmente chamados de **bugs** ou **glitches**) nos programas podem ter consequências complexas e imprevistas. ... A programação no mundo real, no entanto, exige cuidado, experiência e sabedoria."* > > — Harold Abelson e Gerald Jay Sussman, *Structure and Interpretation of Computer Programs* (1985) Se não anteciparmos as consequências do que conjuramos, um agente sem orientação multiplicará pequenas falhas estruturais mais rápido do que podemos nos dar conta. Esse problema não é novo; na verdade, sempre aconteceu mesmo com código escrito por humanos, mas não percebíamos tanto porque era um processo bem mais lento. Alguns tentavam impedir isso e eram sempre tachados de revisores de código pedantes, amados por poucos escolhidos, mas incompreendidos pela maioria. Na psicologia, esse fenômeno é conhecido como a Teoria das Janelas Quebradas, baseada em um experimento de 1969. Pesquisadores deixaram um carro abandonado em um bairro nobre por uma semana. Nada aconteceu com ele. Na segunda semana, um dos pesquisadores usou uma marreta para quebrar uma das janelas. Em poucas horas, o carro foi completamente depenado. O vidro quebrado foi o gatilho. Sinalizou que ninguém se importava, e assim a comunidade, coletivamente, também parou de se importar. Venho defendendo há anos que o mesmo acontece com o código. Uma vez que surge uma janela quebrada no código (por exemplo: erros de digitação, nomes ruins, testes fracos ou abstrações equivocadas), ela ativa nosso mecanismo psicológico que deixa de se importar. E o problema escala com o tempo, fazendo com que pequenas falhas se transformem em bolas de neve. No mundo agêntico, o problema é amplificado, já que o código existente enviesará o modelo ao gerar o novo. Se o código existente for ruim, a menos que devidamente instruído, o agente apenas ampliará seus problemas, pois aquilo é interpretado como um padrão aceitável a ser seguido. Code reviews são a parte menos glamourosa do trabalho de engenharia, a ponto de muitos engenheiros nem considerarem isso como parte do trabalho. No entanto, é uma das mais importantes. Isso **não** significa revisar cada linha de código. Ninguém gosta de revisar milhares de linhas de código gerado por IA, inclusive eu. Bom gosto não é sobre verificar sintaxe ou caçar erros mecânicos — é para isso que servem as ferramentas determinísticas. Como escrevi em [Como Fazer Code Reviews na Era Agêntica](https://danicat.dev/pt-br/posts/20260303-code-reviews-in-2026/), se um compilador, linter ou teste automatizado pode pegar um problema, um ser humano não deveria estar olhando para aquilo. Temos que combater IA com IA e automação: use barreiras determinísticas para eliminar o ruído mecânico, foque seu tempo de revisão nas fronteiras arquiteturais críticas e faça amostragem do restante. Como os tempos de ciclo hoje são ordens de magnitude mais rápidos do que na "programação artesanal", através da amostragem, mesmo que você não pegue um problema de imediato, terá rapidamente outra chance de capturá-lo. Portanto, direcione suas revisões para as partes realmente críticas e confie no processo para pegar o resto. ## Para onde vai o tempo livre prometido? Estamos construindo software mais rápido do que nunca. Ao remover os gargalos de implementação, todas aquelas ideias que costumavam mofar na gaveta até serem substituídas por algo mais urgente de repente se tornaram viáveis. Agora podemos escrever protótipos em rápida sucessão para ver o que funciona e o que não funciona, muito antes de irem parar na pilha de projetos abandonados. Mover-se mais rápido, contudo, não substitui a necessidade de construir modelos mentais. Nosso trabalho central é estruturar contexto, restrições e ciclos de feedback para que os agentes construam o que realmente pretendíamos. Se mudou algo, é que nosso trabalho ficou ainda mais exigente: o conhecimento técnico se tornou uma commodity, mas o bom gosto e o senso crítico continuam escassos. Em última análise, ainda somos feiticeiros conjurando os espíritos dos computadores. Foram apenas nossos feitiços que cruzaram mais um nível de abstração. --- # Projetando Interfaces Agênticas com A2UI e Genkit - **Markdown URL**: https://danicat.dev/pt-br/posts/designing-agentic-interfaces-with-a2ui-and-genkit/index.md - **HTML URL**: https://danicat.dev/pt-br/posts/designing-agentic-interfaces-with-a2ui-and-genkit/ - **Date**: 2026-10-27 - **Categories**: Agent Development - **Tags**: a2ui, agent-skills, ai-agents, gemini, genkit, golang, osquery - **Series**: Gemini for Go Developers - **Summary**: Aprenda a transformar um agente Genkit em Go de um simples chatbot de texto em uma interface generativa usando A2UI, catálogos de componentes personalizados e interrupções human-in-the-loop. No [artigo anterior](https://danicat.dev/pt-br/posts/20261006-building-agentic-backends-with-genkit-go/), reconstruímos o backend da AIDA, a Agente de Diagnóstico por IA, substituindo sua configuração legada de RAG em Python e SQLite por um binário compilado em Genkit Go e Agent Skills sob demanda. Com o backend concluído, nossa atenção se volta para o frontend. Mesmo com um novo cérebro em Go e skills sob demanda, a AIDA ainda é praticamente um chatbot: você digita um prompt, ela executa o `osquery` e despeja uma parede de texto de volta para você. Embora a AIDA tenha uma temática retrô, não precisamos limitá-la a uma experiência tão ultrapassada. Interfaces agênticas modernas não devem apenas fazer uso de elementos de interface mais ricos, mas também se adaptar às necessidades do usuário em vez de aplicar uma abordagem estática de tamanho único. Neste artigo, modernizamos a interface da AIDA. Antes de nos aprofundarmos no código, vamos analisar o que uma UI agêntica moderna exige e como funciona o protocolo [Agent to User Interface (A2UI)](https://a2ui.org/). ## O que é o A2UI? O A2UI é um protocolo aberto do Google que permite que agentes de IA se comuniquem diretamente usando componentes nativos de interface do usuário. Em vez de limitar as respostas a texto, um agente pode montar dinamicamente painéis, gráficos, tabelas e controles interativos adaptados a qualquer tarefa que você esteja realizando no momento. Seu frontend mantém total controle sobre o estilo visual e a renderização da interface, enquanto o agente escolhe *o que* exibir com base no contexto da conversa e nos dados. Uma integração com A2UI gira em torno de três conceitos principais: - **Catálogo (Catalog)**: O menu compartilhado de blocos de construção de UI (como painéis, medidores, gráficos, tabelas e botões) e seus esquemas. Tanto o agente no backend quanto o cliente no frontend concordam previamente com o catálogo — você pode usar o catálogo básico embutido ou definir componentes customizados sob medida para o seu domínio. - **Superfícies (Surfaces)**: O canvas dinâmico de interface (``) renderizado no cliente. Conforme o agente trabalha, ele transmite mensagens estruturadas em JSON (`application/a2ui+json`) indicando ao cliente quais componentes do catálogo posicionar na superfície e como atualizar suas propriedades em tempo real. - **Ações (Actions)**: Eventos bidirecionais enviados de uma superfície de volta ao agente quando o usuário interage com um componente — como clicar em um botão, selecionar uma linha em uma tabela ou inspecionar um nó em um grafo. As ações transformam cliques diretamente no próximo turno da conversa, sem a necessidade de digitar um prompt de acompanhamento. ## A nova especificação de frontend da AIDA Queremos migrar de uma interface hardcoded para algo que o agente possa definir livremente conforme o contexto, utilizando o A2UI. Isso exige definirmos um catálogo de componentes para a AIDA e conceder a ela acesso a esses blocos de construção. Também precisamos reestruturar o frontend para que ele consiga renderizar adequadamente as superfícies A2UI. Aqui está o novo cockpit de diagnóstico em ação. Em tempo de execução, a AIDA monta um grafo de processos em 3D, medidores estilo heads-up display (HUD) e uma tabela dinâmica de processos do `osquery`: ![AIDA renderizando uma superfície de diagnóstico A2UI v0.9 em tempo real com grafo de processos 3D, medidores HUD e uma matriz interativa de processos do osquery](aida-diagnostic-surface.png "Superfície de diagnóstico da AIDA renderizada com componentes A2UI customizados: grafo de processos em 3D, medidores HUD e tabela de processos do osquery em tempo real.") O prompt a seguir traduz essa intenção em uma especificação: ```markdown Upgrade AIDA's web interface from a static chat UI to a generative diagnostic cockpit using A2UI and Genkit. Requirements: 1. Keep the retro NEC PC-98 cyberpunk look and feel (AIDA's pixel avatar, CRT scanlines, and neon palette), with a fixed host-status sidebar showing live osquery system vitals. 2. Replace the plain Markdown chat output with an A2UI surface powered by a custom "aida" component catalog for system diagnostics: HUD panels, gauges, charts, filterable data tables, 3D system visualisations, text blocks, and action buttons. 3. Wire up two-way A2UI actions so clicking a table row, 3D node, or button sends a follow-up diagnostic action back to AIDA. 4. Add a human-in-the-loop shell command tool so AIDA can actually fix issues she finds on the host, pausing for explicit Approve/Deny confirmation in the UI before executing any command. /grill-me ``` Não espere acertar tudo de primeira apenas com esse prompt. Na verdade, é fundamental fornecer ao seu agente de programação as skills, MCPs e ferramentas adequadas para a tarefa, como veremos na seção "Skills recomendadas" a seguir. Você também pode notar a dica da skill `/grill-me` no final do prompt. Essa skill vem embutida no Antigravity (e na maioria dos agentes de codificação modernos), e ativá-la instrui o agente a fazer perguntas sempre que as instruções forem ambíguas ou faltarem detalhes. Isso ajuda a refinar sua visão e tira a pressão de ter que escrever um prompt impecável desde o início. Como o prompt acima é bastante conciso, a skill `/grill-me` assume boa parte do trabalho pesado. É por isso que podemos nos dar ao luxo de não especificar completamente o catálogo de componentes (como veremos abaixo): o Antigravity nos fará perguntas para preencher as lacunas do prompt. ### Skills recomendadas Como estamos trabalhando com Genkit, recomendo que você instale e ative as [Genkit Skills](https://github.com/genkit-ai/skills) oficiais, incluindo `developing-genkit-go` e `developing-genkit-js`. Essas skills contêm não apenas o conhecimento de como construir agentes e fluxos, mas também de como trabalhar com os elementos do A2UI tanto no lado do cliente quanto no do servidor. Além dessas, a skill `gemini-api-dev` ajuda o agente a utilizar modelos modernos em vez de recorrer a versões defasadas. Essa skill pode ser instalada isoladamente a partir do repositório [Google Skills](https://github.com/google-gemini/gemini-skills/blob/main/skills/gemini-api-dev/SKILL.md) ou como parte do [Gemini Cloud Developer Plugin](https://cloud.google.com/blog/topics/developers-practitioners/introducing-the-google-cloud-developer-plugin-for-ai-coding-agents). ## Design do catálogo A2UI O desenvolvimento frontend tradicional envolve projetar a interface do usuário em torno das jornadas de usuário que desejamos atender. Isso não é diferente em aplicações A2UI, mas aqui não desenharemos telas de aplicação clássicas e estáticas. Em vez disso, vamos nos concentrar em projetar os componentes individuais necessários para alcançar nossos objetivos. Esses componentes compõem o **catálogo** do agente e dão a ele a flexibilidade necessária para adaptar a UI sob demanda. Você não **precisa** necessariamente criar um catálogo do zero, pois o A2UI já vem com um **Catálogo Básico** que atende à maioria das necessidades comuns. Mas como nosso objetivo aqui era criar uma experiência única, projetar um catálogo customizado foi a melhor escolha para a AIDA. Estes são os sete componentes que desenhamos para a AIDA: - **Panel**: Um contêiner HUD moldurado que agrupa componentes relacionados em linhas, colunas ou grades, com um badge de status (`nominal`, `warning` ou `critical`) para identificar subsistemas com problemas rapidamente. - **Scene3D**: Um canvas interativo com wireframe em 3D para visualizar topologias de sistema — como árvores de processos, núcleos de CPU, matrizes de memória, malhas de rede, setores de disco ou zonas térmicas — com nós clicáveis para inspeção detalhada. - **CyberChart**: Um gráfico com múltiplas séries (linhas, barras, áreas, dispersão ou radar) para plotar métricas como uso de CPU, throughput de rede e latência. - **HudGauge**: Um medidor visual (arco circular, barra de plasma ou barra segmentada) para métricas de capacidade e porcentagem, como uso de disco, carga da bateria e pressão de swap. - **DataMatrix**: Uma tabela com suporte a ordenação e filtros para resultados do `osquery`, onde clicar em qualquer linha dispara uma inspeção subsequente naquele item. - **TextBlock**: Uma nota de diagnóstico monoespaçada para resumos, saídas de comandos e trechos copiáveis de código. - **ActionButton**: Um botão interativo que permite à AIDA sugerir ações corretivas em um clique, como inspecionar um processo suspeito ou executar uma correção. Projetar instrumentos específicos de domínio como esses faz uma enorme diferença, já que o catálogo básico foca principalmente na funcionalidade em detrimento da estética e de experiências mais ricas para o usuário. ## Configurando o A2UI no backend em Go Para dar à AIDA acesso aos nossos novos blocos de construção de UI no backend, habilitamos o plugin A2UI do Genkit na inicialização e carregamos nosso catálogo personalizado de componentes junto com as ferramentas de diagnóstico dela: ```go // internal/agent/agent.go g := genkit.Init(ctx, genkit.WithExperimental(), genkit.WithPromptDir("./prompts"), genkit.WithDefaultModel("vertexai/gemini-3.8-flash"), genkit.WithPlugins( &googlegenai.VertexAI{}, &middleware.Middleware{}, &a2uix.A2UI{}, // Registra o middleware A2UI ), ) // 1. Registra o catálogo customizado de componentes A2UI "aida" if _, err := a2uix.LoadCatalog(g, AidaCatalog()); err != nil { return nil, err } // 2. Registra diagnósticos somente leitura (osquery) e comandos de shell HITL (command) osquery.RegisterTool(g, runner) command.RegisterTool(g) // 3. Define o agente AIDA a partir de prompts/aida.prompt aidaAgent := genkitx.DefinePromptAgent(g, "aida", aix.WithDescription("AIDA — AI Diagnostic Agent"), aix.WithSessionStore(sessionStore), aix.WithNamedPrompt("aida", PromptInput{OS: runtime.GOOS}), ) ``` Aqui inicializamos o Genkit com `&a2uix.A2UI{}` junto aos plugins do Gemini Enterprise (Vertex AI) e de middlewares, registramos nossas ferramentas de diagnóstico e remediação, e associamos o agente prompt `aida` ao arquivo `prompts/aida.prompt`. A adição fundamental é `a2uix.LoadCatalog(g, AidaCatalog())`, que registra nosso catálogo de componentes com o runtime do Genkit para que o middleware A2UI possa expor esses componentes ao modelo e validar sua saída. ### Definindo o catálogo e o middleware A2UI Em Go, um catálogo customizado de A2UI (`a2uix.Catalog`) é uma lista de definições de componentes onde cada entrada especifica o `Name` do componente, uma `Description` legível por humanos e suas propriedades esperadas (`Props`): ```go // internal/agent/catalog.go func AidaCatalog() *a2uix.Catalog { return &a2uix.Catalog{ ID: "aida", Components: []a2uix.CatalogComponent{ { Name: "HudGauge", Description: "A visual meter for capacity and percentage metrics.", Props: "label: string (required); value: number (required); unit?: string; status?: 'nominal' | 'warning' | 'critical'", }, { Name: "ActionButton", Description: "An interactive button that triggers a follow-up diagnostic or remediation action.", Props: "label: string (required); action: string (required); variant?: 'primary' | 'warning' | 'danger'", }, // ... Panel, Scene3D, CyberChart, DataMatrix, TextBlock }, } } ``` No backend, o A2UI opera como um middleware do Genkit. Em vez de retornar balões de conversa normais em Markdown, o modelo emite blocos delimitados de JSON com a tag `a2ui` descrevendo os componentes que deseja renderizar. O middleware intercepta esses blocos durante o streaming, valida-os contra o catálogo registrado e os empacota como mensagens `application/a2ui+json` para o navegador. Configuramos essa pipeline de forma declarativa em `prompts/aida.prompt`: ```yaml --- model: vertexai/gemini-3.8-flash description: AIDA — AI Diagnostic Agent input: schema: os: string tools: - run_osquery - runCommand config: thinkingConfig: thinkingLevel: LOW tools: - googleSearch: {} use: - genkit-middleware/skills: skillPaths: - ./skills - a2ui: catalogId: aida validate: strict --- ``` Por padrão, o middleware `a2ui` lê as descrições dos componentes e as especificações de propriedades do catálogo `"aida"` registrado e as injeta diretamente no system prompt (`instructions: system`). Isso mantém `internal/agent/catalog.go` como a única fonte da verdade — sempre que ajustamos um componente em Go, o prompt é atualizado automaticamente sem manutenção manual de schemas. Conforme o modelo transmite os blocos `a2ui`, `validate: strict` confere se cada mensagem JSON possui um envelope A2UI válido e se utiliza apenas tipos de componentes pertencentes ao catálogo `"aida"`. O fluxo falha imediatamente caso o modelo invente um componente inexistente ou gere um JSON malformado. ## Construindo o frontend generativo Assim que o backend em Go passa a transmitir mensagens A2UI validadas, o navegador precisa de um runtime cliente para transformar esses payloads JSON em elementos do DOM. O frontend em `web/` é uma aplicação leve em TypeScript e [Lit](https://lit.dev/) encarregada de três funções: 1. **Equiparar o catálogo de componentes**: O cliente registra cada um dos sete instrumentos `"aida"` como um web component Lit (`web/src/aida-catalog/`), pareado com um schema de propriedades no lado do cliente. Como o backend em Go valida apenas os nomes dos tipos de componentes, o navegador usa esse schema para validar as propriedades recebidas, definir valores padrão e vincular dados antes de renderizar. 2. **Renderizar superfícies a partir da stream**: O SDK de cliente do Genkit se conecta ao endpoint `/agents/aida` e encaminha as mensagens A2UI recebidas para um `MessageProcessor` do A2UI. O processador atualiza dinamicamente o elemento `` na página à medida que os chunks chegam. 3. **Enviar ações do usuário de volta ao agente**: Clicar em um botão de ação, em uma linha de tabela ou em um nó 3D despacha uma ação A2UI. O cliente encaminha esse evento de volta ao backend em Go como o próximo turno da conversa: ```ts // web/src/aida-cockpit.ts const agent = remoteAgent({ url: "/agents/aida" }); const chat = agent.chat(); const processor = new MessageProcessor([aidaCatalog], async (action) => { // Encaminha cliques da UI (botões, linhas de tabela, nós 3D) de volta ao agente Go const turn = chat.sendStream({ message: actionToMessage(action as any) }); await consumeTurnStream(turn); }); ``` Quando o cockpit é aberto pela primeira vez, a barra lateral esquerda consulta `/api/system-status` para exibir dados vitais do `osquery` em tempo real, enquanto a área principal apresenta atalhos rápidos de diagnóstico: ![Tela inicial do cockpit de diagnóstico da AIDA com avatar em pixel art estilo PC-98, barra lateral de status do sistema via osquery em tempo real e cards de atalho para diagnósticos rápidos](aida-cockpit-landing.png "Tela inicial do cockpit da AIDA com sinais vitais do sistema em tempo real na barra lateral e cards de diagnóstico rápido.") Clicar em um atalho ou digitar uma pergunta envia o prompt para `/agents/aida`. Conforme a AIDA transmite os resultados do `osquery`, o `MessageProcessor` substitui a tela de atalhos pela superfície de diagnóstico interativa que vimos antes. ## Human-in-the-loop com interrupções de agente Consultar uma máquina via `osquery` é uma operação estritamente de leitura, o que torna seguro para a AIDA executar queries de forma autônoma. Com ações bidirecionais de A2UI, no entanto, ela também pode renderizar um `ActionButton` oferecendo a correção de um problema detectado — como encerrar um processo travado ou limpar um cache. Comandos de shell no host não devem rodar sem supervisão. Para resguardar essas ações, o Genkit oferece **interrupções de ferramentas (tool interrupts)** nativas no modelo human-in-the-loop: a ferramenta suspende o turno atual, aguarda a confirmação explícita do usuário e só então executa o efeito colateral solicitado. ### Pausando e retomando a execução de ferramentas Em Go, definimos `runCommand` usando `genkitx.DefineInterruptibleTool`. Essa função recebe um payload opcional de confirmação indicando se a AIDA está chamando a ferramenta pela primeira vez ou retomando após a entrada do usuário: 1. **Chamada inicial (pausa):** Quando a AIDA chama `runCommand` pela primeira vez, o payload de confirmação é `nil`. Em vez de executar o comando shell, a ferramenta pausa o turno retornando `tool.Interrupt(...)` com o comando sugerido, o motivo de executá-lo e o nível de risco. 2. **Chamada retomada (execução ou rejeição):** Quando o usuário clica em **Aprovar (Approve)** ou **Negar (Deny)** na interface, o Genkit invoca a função da ferramenta novamente com a decisão do usuário: ```go // internal/command/tool.go package command func RegisterTool(g *genkit.Genkit) { genkitx.DefineInterruptibleTool(g, "runCommand", ai.WithDescription("Runs a shell command on the host with operator approval"), func(ctx *ai.ToolContext, input CommandInput, res *CommandConfirmation) (CommandOutput, error) { // 1. Primeira chamada: pausa o turno e solicita aprovação ao operador if res == nil { return CommandOutput{}, tool.Interrupt(CommandInterrupt{ Command: input.Command, Reason: input.Reason, Risk: input.Risk, }) } // 2. Chamada retomada: se negado, retorna feedback estruturado em vez de erro if !res.Approved { return CommandOutput{ Command: input.Command, Status: "rejected", Stderr: fmt.Sprintf("Operator denied execution: %s", res.Feedback), Executed: false, }, nil } // 3. Aprovado: executa o comando com timeout de 30 segundos execCtx, cancel := context.WithTimeout(ctx.Context, 30*time.Second) defer cancel() out, err := exec.CommandContext(execCtx, "sh", "-c", input.Command).CombinedOutput() // ... retorna CommandOutput com stdout e código de saída }, ) } ``` Como `tool.Interrupt` serializa a struct `CommandInterrupt` (`Command`, `Reason` e `Risk`) diretamente na stream de resposta ao pausar o turno, o frontend recebe tudo o que precisa para exibir o prompt de confirmação ao usuário sem a necessidade de criar endpoints adicionais de API. ### Aprovando ou negando comandos no cockpit Quando o `runCommand` dispara uma interrupção, o turno em streaming é pausado e o navegador recebe o payload de interrupção no objeto de resposta. O cockpit altera o badge de status da AIDA para `APPROVAL REQUIRED` e exibe um card de aprovação embutido. Esse card apresenta o comando exato, o motivo e o nível de risco, além de um campo opcional de feedback e os botões de **Aprovar (Approve)** / **Negar (Deny)**: ![Cockpit da AIDA pausado em uma interrupção human-in-the-loop do runCommand exibindo o comando shell proposto, badge de risco, campo de feedback e botões de aprovar ou negar](aida-hitl-interrupt.png "Cockpit pausado em uma interrupção de ferramenta do Genkit, aguardando confirmação do usuário antes de executar o comando shell.") Clicar em qualquer um dos botões vincula sua decisão à requisição da ferramenta interrompida e chama `chat.resumeStream(...)` no cliente Genkit. O backend em Go então conclui a execução (ou o cancelamento) do comando e transmite o painel de verificação da AIDA — com medidores atualizados e a saída do comando — diretamente para a superfície: ![Cockpit da AIDA após a aprovação da interrupção do runCommand, exibindo a superfície de verificação A2UI com medidores de capacidade de disco e saída do comando shell](aida-hitl-resumed.png "Execução retomada após a aprovação: a AIDA executa o comando e renderiza o painel de verificação na superfície.") ## Conclusões Concluir a reescrita da AIDA no repositório [`go-aida`](https://github.com/danicat/go-aida) consolida a arquitetura completa. Ao longo destes dois artigos, substituímos o antigo protótipo em Python com RAG em SQLite e uma janela de chat estática por um serviço em Genkit Go em binário único com um cockpit de diagnóstico dinâmico em A2UI. Duas lições de design se destacaram ao longo desta etapa: 1. **Catálogos específicos de domínio superam blocos genéricos de UI:** Fornecer à AIDA sete instrumentos de diagnóstico sob medida transformou suas respostas de muralhas de texto Markdown em cards aninhados em um verdadeiro dashboard interativo de sistema. O catálogo atua como o contrato compartilhado entre as duas pontas: sempre que você atualiza um componente no frontend, atualiza o catálogo em Go correspondente (e vice-versa). Por sua vez, o middleware `a2ui` injeta esses schemas no system prompt e impõe `validate: strict`, garantindo que o modelo sempre renderize instrumentos válidos e suportados. 2. **Interrupções de ferramentas tornam as correções no host seguras e interativas:** As tool interrupts do Genkit permitem que o backend em Go faça uma pausa antes de executar um comando shell e retome com fluidez assim que o usuário aprova ou nega a ação. Retornar um feedback estruturado de recusa em vez de um erro fatal permite que o agente se adapte e sugira opções mais seguras sem interromper o fluxo da conversa. --- # Criando Ferramentas Visuais com Antigravity UI Extensions - **Markdown URL**: https://danicat.dev/pt-br/posts/building-sidecars-and-ui-extensions-in-antigravity/index.md - **HTML URL**: https://danicat.dev/pt-br/posts/building-sidecars-and-ui-extensions-in-antigravity/ - **Date**: 2026-10-13 - **Categories**: Agentic Coding - **Tags**: agent-skills, antigravity, gemini, tutorial, vibe-coding - **Summary**: Conheça o novo recurso de UI Extensions no Google Antigravity 2.22.0 e aprenda a criar ferramentas visuais bidirecionais no painel lateral. Eu costumo ser bem pé no chão nos meus posts, mas desta vez estou *realmente* empolgada. Na semana passada, a equipe do Antigravity lançou o [Antigravity 2.22.0](https://antigravity.google/releases?tab=hub&version=2.22.0), que traz um recurso novo que explodiu a minha cabeça: o suporte a **UI Extensions** em plugins do Antigravity. Por que isso é importante? Porque muda toda a experiência de desenvolvimento com agentes de programação para melhor. Vamos ver como. As UI Extensions permitem renderizar interfaces de usuário personalizadas dentro do painel lateral do Antigravity. O principal motivo pelo qual isso é incrível é que agora podemos ampliar a interface do Antigravity com todo tipo de recurso, trazendo para dentro da ferramenta funcionalidades que antes exigiriam aplicativos separados para executar, como edição de imagens, testes de software, mineração de logs e análise de dados. Por exemplo, imagine que você está desenhando a interface do seu próximo aplicativo. Explicar isso em texto quase sempre é uma experiência horrível. Com um desenho você consegue ser muito mais preciso do que com texto e o Gemini, sendo multimodal, entende o desenho nativamente. Antes das UI Extensions, eu abria um editor de imagens ou uma ferramenta de diagramas como o [Excalidraw](https://excalidraw.com/), fazia o meu rascunho e depois copiava e colava no meu prompt. Não era uma experiência terrível em si, mas era meio trabalhosa. Agora, com as UI Extensions, posso ter uma ferramenta de diagramação completa dentro da interface do Antigravity e, com um clique de botão, enviar imagens anotadas diretamente para a sessão do meu agente de programação, que reage a elas imediatamente. Isso é apenas a ponta do iceberg. A capacidade de anotar o seu trabalho e explicar problemas visualmente, com imagens, logs e todo tipo de dado de apoio, ali mesmo sem sair do Antigravity, foi o que me convenceu de que finalmente estamos "vivendo no futuro". Para provar isso para você, construí dois plugins open-source com UI Extensions que você pode testar agora mesmo: o [**Antigravity Paint Studio (`agy-paint-studio`)**](https://github.com/danicat/agy-paint-studio) para desenho em múltiplas camadas, edição e refinamento de imagens com IA, e o [**Antigravity Game Studio (`agy-game-studio`)**](https://github.com/danicat/agy-game-studio) para criar e depurar visualmente jogos 2D em Go. Vamos ver como eles funcionam por baixo dos panos. ## Anatomia de uma UI extension Uma UI extension é um plugin contendo um [**sidecar**](https://antigravity.google/docs/sidecars/) que expõe painéis web interativos e pode ser renderizado dentro da interface do Antigravity. Assim como plugins normais, ela também pode empacotar skills, MCPs e regras, permitindo construir experiências completas em torno de um determinado domínio (como edição de imagens). Os plugins podem ser instalados tanto no nível do workspace (em `.agents/plugins`) quanto no nível global (em `~/.gemini/config/plugins`). De forma semelhante às skills, eles precisam seguir uma estrutura de diretórios específica. ```text plugins// ├── plugin.json # Arquivo obrigatório de manifesto e identificação ├── mcp_config.json # Definições opcionais de servidores MCP ├── hooks.json # Definição opcional de hooks ├── skills/ # Diretório opcional de skills │ └── / │ └── SKILL.md ├── agents/ # Templates opcionais de definição de subagentes │ └── .md ├── rules/ # Diretório opcional de regras │ └── .md └── sidecars/ └── / └── sidecar.json # Obrigatório para UI extensions ``` Embora sidecars comuns possam ser escritos em qualquer linguagem, para as UI extensions vamos utilizar [Node.js](https://nodejs.org/), pois o Antigravity já vem com um **Sidecar SDK** embutido que simplifica a comunicação entre o processo principal do agente e o processo do sidecar. Coisas como descoberta de portas e troca de mensagens são resolvidas pelo SDK como num passe de mágica. Como nota à parte: na verdade, é possível alcançar a mesma experiência sem usar Node.js, o Sidecar SDK ou qualquer suporte nativo a UI Extensions, como mostrei na minha [palestra State of Agentic Coding no Devoxx Belgium](https://www.youtube.com/watch?v=nHRSwiFNQ9E). O problema da abordagem manual é que você acaba tendo que lidar com alocação de portas (e possíveis conflitos), além de descoberta e comunicação de sessão do jeito difícil. O Sidecar SDK torna tudo isso muito mais simples. ![Arquitetura de uma UI extension e sidecar no Antigravity](architecture-diagram.png "Como o Antigravity supervisiona o processo sidecar de um plugin e conecta o painel lateral auxiliar à conversa ativa") O ponto de entrada é `sidecars//sidecar.json`, que informa ao Antigravity como iniciar o processo em segundo plano e renderizar sua interface web no painel lateral: ```json { "command": "node", "args": ["main.mjs"], "restart_policy": "always", "display_name": "My Extension", "has_web_ui": true, "ui_config": { "views": [ { "path": "/", "entrypoint": "SIDECAR_UI_ENTRYPOINT_AUX_PANE", "title": "My Extension" } ] } } ``` Aqui vale destacar o atributo `has_web_ui` e as propriedades de `ui_config`, pois são eles que transformam um sidecar comum em uma UI extension. Como `"command": "node"` utiliza o runtime Node.js embutido do Antigravity e o `sidecar_sdk`, sua extensão não precisa de `npm install` nem de `node_modules`. Um backend completo em `main.mjs` leva apenas algumas linhas de JavaScript: ```javascript import { readFileSync } from 'node:fs'; import { SidecarApp } from 'sidecar_sdk'; const app = new SidecarApp(); // Re-reading on each request lets edits appear when you reopen. app.page('/', () => readFileSync(new URL('./index.html', import.meta.url), 'utf8') ); // POST routes return JSON and verify the sidecar token automatically. app.api('/api/save', (data) => ({ saved: true, name: data.name })); app.run(); ``` No frontend, adicionar `` ao `` do seu HTML sincroniza as variáveis de tema claro e escuro do Antigravity e expõe o objeto `window.sidecar`, permitindo que sua interface chame o backend (`fetch()`), inspecione o workspace aberto (`getWorkspaceUris()`) e envie mensagens direto para o turno ativo do chat (`agent.sendMessage()`). Uma vez ativada, você pode abrir o painel pelo menu **⋮** em **Extensions**, clicando em qualquer arquivo correspondente a `file_extensions`, ou por meio de um atalho clicável `sidecar://` no chat. ## Criando suas próprias UI extensions do jeito fácil Esses detalhes de implementação são ótimos quando você precisa resolver algo no braço, mas, sinceramente, espero que nenhum de vocês escreva essas extensões manualmente. Isso porque o Antigravity é "autoconsciente": ele sabe como construir essas extensões e você só precisa pedir para ele fazer isso por você. Se quiser ser bem explícito, pode pedir para ele invocar as skills `/plugin` e `/ui-extension`. Se quiser "ensinar" a ele sobre personalizações em geral, você também pode usar a skill `/agy-customizations`. Foi assim que construí tanto o "Antigravity Paint Studio" quanto o "Antigravity Game Studio". Vamos dar uma olhada nas duas extensões agora. ## Editando imagens sem sair do Antigravity com o Paint Studio O **Paint Studio** nasceu exatamente daquele caso de uso que mencionei na introdução: eu queria poder descrever meus problemas usando imagens ("um desenho vale mais que mil palavras"), mas sem trocar de contexto. Isso significava ter um lugar dentro do Antigravity onde eu pudesse rabiscar uma ideia rápida do zero, ou clicar em qualquer imagem do meu workspace ou sessão de chat para abri-la no painel lateral, anotar algumas observações por cima e enviá-la de volta para o agente. ![Paint Studio acoplado ao painel lateral auxiliar refinando o rascunho de um gato em uma ilustração em aquarela](paint-studio-sidepane.png "Paint Studio acoplado ao painel lateral auxiliar, transformando o rabisco de 10 segundos de um gato à esquerda em uma ilustração em aquarela à direita") O que torna essa configuração muito divertida é que você acaba tendo **IA dos dois lados** do fluxo de trabalho. Você tem o seu agente de programação no painel de chat à esquerda e também tem o Gemini (neste caso, o Nano Banana 2 Lite, também conhecido como `gemini-3.1-flash-lite-image`) integrado diretamente ao Paint Studio à direita. Na captura de tela acima, desenhei aquele gatinho "smol void" em cerca de dez segundos; depois, após algumas etapas de refinamento, o Nano Banana 2 Lite o transformou na pintura em aquarela que você vê no painel lateral. Vale mencionar que o Paint Studio também aproveita outro recurso de interface bem interessante do Antigravity: as interfaces generativas (aliás, [o meu primeiro protótipo do Paint Studio](https://x.com/danicat83/status/2100298197931372652) foi feito puramente com `/generative_ui`). As Generative UIs diferem das UI Extensions porque são renderizadas inline dentro da própria janela de chat. Elas são ativadas pela skill `/generative_ui` e você pode usá-las tanto para criar superfícies reutilizáveis (como as do Paint Studio) quanto descartáveis, por exemplo, para uma visualização pontual de dados. [Post on X](https://x.com/danicat83/status/2100298197931372652) A Generative UI funciona emitindo um bloco `` que renderiza um iframe em sandbox com limite de 500px de altura. Você pode colocar **quase** qualquer coisa dentro desse iframe usando JavaScript inline e as classes semânticas do Tailwind fornecidas pelo host (como `bg-[var(--card)]` e `text-[var(--foreground)]`). No entanto, sua Content Security Policy (CSP) bloqueia todas as CDNs externas, exceto pelo script do Tailwind embutido no Antigravity (`https://www.gstatic.com/antigravity/web/dev/tailwindcss.min.js`), e não permite a execução de WebAssembly (WASM). Para o Paint Studio, tenho um endpoint específico capaz de renderizar tanto a interface completa (para o painel lateral ou modo destacado) quanto uma versão simplificada (para o painel inline via `/generative_ui`). Incluí no plugin a skill `/paint-studio`, que você pode acionar para instruir o agente a abrir o estúdio, garantindo um atalho prático sempre que precisar de um diagrama rápido. ![Canvas inline do Paint Studio renderizado via /paint-studio e /generative_ui mostrando o rascunho do gato smol void](paint-studio-inline.png "Invocar a skill /paint-studio carrega um painel inline via /generative_ui para rascunhos rápidos") Para ainda mais praticidade, você pode registrar tipos de arquivo que abrem com a sua extensão no `sidecar.json` (como `*.psd`, `*.png` e outros), e o aplicativo também pode ser iniciado com uma tela em branco pelo menu de extensões: ```json { "ui_config": { "display_name": "Paint Studio", "views": [ { "path": "/", "entrypoint": "SIDECAR_UI_ENTRYPOINT_AUX_PANE", "title": "Paint Studio", "file_extensions": [".psd", ".png", ".jpg", ".jpeg", ".webp"] } ] } } ``` Dessa forma, garantimos uma comunicação bidirecional completa: podemos clicar em qualquer arquivo para abri-lo no estúdio, fazer anotações, retoques e melhorias com IA, e enviar para o chat com o clique de um botão. ## Testando e depurando jogos 2D com o Antigravity Game Studio Desde que criei minhas skills de desenvolvimento de jogos, venho pensando em como levar isso para o próximo nível. Skills, que são basicamente prompts, só conseguem te levar até certo ponto. No desenvolvimento de jogos, existe um componente visual enorme, que vai desde a direção de arte, criação de assets e design de fases até o HUD e tantos outros detalhes. Depois de explorar a experiência redesenhada com o Paint Studio, comecei a pensar em como poderíamos redesenhar a experiência de desenvolvimento para criadores de jogos também, e foi assim que surgiu o **Antigravity Game Studio**. No começo, tentei empacotar todo tipo de ferramenta nele, mas acabou virando uma grande bagunça. Então decidi focar na funcionalidade que mais se beneficiaria de feedback visual do que qualquer outra: o playtesting. A UI extension — **Playtest Studio** — faz uma única coisa: ela compila o seu jogo em Go para WebAssembly e permite jogar e depurá-lo ao vivo a 60 FPS ali mesmo no painel lateral. Enquanto você joga, o Playtest Studio grava um **buffer de replay contínuo de 10 segundos** (como um DVR). ![Playtest Studio pausado no modo de anotação DVR](playtest-studio-annotate.png "Pausando um jogo no Playtest Studio, voltando na linha do tempo de 10 segundos e desenhando uma anotação sobre um bug de spawn") Agora, sempre que vejo algo estranho acontecer no jogo, o ciclo de depuração leva segundos: 1. Pressiono `F8` (ou `` ` ``) para **Pausar** e volto quadro a quadro na linha do tempo de 10 segundos até o momento exato em que o bug aconteceu. 2. Desenho uma caixa ou seta diretamente sobre o sprite com problema, ou escrevo uma nota rápida na margem ao redor da tela. 3. Clico em **📸 Frame** ou **🎞️ 10s GIF** para enviar a captura de tela anotada (ou um replay animado de 10 segundos em GIF), junto com os logs de console capturados do WebAssembly, direto para a sessão de chat para o agente corrigir. O estúdio envia de volta ao agente uma mensagem estruturada contendo todas essas informações para que ele possa trabalhar na correção imediatamente, com muito menos ambiguidade do que um prompt em texto. ![Mensagem estruturada de relatório de bug enviada pelo Playtest Studio na sessão de chat do Antigravity](playtest-bug-report.png "Captura de tela do momento em que o Antigravity recebe uma mensagem do Playtest Studio com um relatório de bug") Você pode ver todo esse fluxo de trabalho em ação no vídeo abaixo, onde abro o Playtest Studio para dar minha primeira olhada em um jogo 2D de Tower Defense que o agente tinha acabado de construir, envio feedback visual e um GIF de 10 segundos para o chat e rebalanceio a economia do jogo na hora: [YouTube Video](https://www.youtube.com/watch?v=A2zOv5Xoh0M) ## Considerações finais Poder criar ferramentas visuais personalizadas que vivem bem ao lado do seu agente — e conversam com ele com um único clique — muda completamente o que um ambiente de programação pode ser. Em vez de ficar alternando de contexto para aplicativos externos e copiando e colando capturas de tela nos prompts, você pode moldar a interface do Antigravity em torno de qualquer problema que esteja resolvendo hoje. Se você quiser testar esses plugins ou usá-los como inspiração para pedir ao Antigravity que construa suas próprias UI extensions, ambos os repositórios estão disponíveis no GitHub: [**`danicat/agy-paint-studio`**](https://github.com/danicat/agy-paint-studio) e [**`danicat/agy-game-studio`**](https://github.com/danicat/agy-game-studio). Bons rascunhos! --- # Por Que Reescrevi Meu Agente RAG em Python Usando Go e Genkit - **Markdown URL**: https://danicat.dev/pt-br/posts/building-agentic-backends-with-genkit-go/index.md - **HTML URL**: https://danicat.dev/pt-br/posts/building-agentic-backends-with-genkit-go/ - **Date**: 2026-10-06 - **Categories**: Agent Development - **Tags**: agent-skills, ai-agents, gemini, genkit, golang, osquery, rag - **Series**: Gemini for Go Developers - **Summary**: Veja como substituí um agente legado em Python com SQLite RAG por um binário único em Go usando Genkit, templates dotprompt tipados e Agent Skills sob demanda. Há pouco mais de um ano, construí a [AIDA, a Agente de Diagnóstico por IA](https://danicat.dev/pt-br/posts/20250531-diagnostic-agent/), o primeiro agente autônomo que criei. A missão dela é diagnosticar problemas no computador consultando o sistema operacional por meio do [osquery](https://osquery.io/). Embora a AIDA tenha passado por algumas atualizações ao longo dos meses, nunca havia questionado sua arquitetura de verdade até recentemente. Quando construí a AIDA originalmente em Python, os modelos estavam em outro patamar de capacidade (`gemini-2.5-flash` na época) e técnicas como [Agent Skills](https://agentskills.io) ainda não haviam sido inventadas. Como o `osquery` expõe centenas de tabelas que variam entre macOS, Linux e Windows, construí uma [solução local de RAG baseada em SQLite](https://danicat.dev/pt-br/posts/20251103-building-aida-part-2/) para injetar definições de schemas sob demanda no contexto. Mesmo sendo um conjunto de dados pequeno e estático (uma tabela por chunk, com embeddings gerados uma única vez), montar um banco vetorial para isso era trabalhoso, e o modelo nem sempre formulava as melhores consultas para a ferramenta de RAG, às vezes recuperando as tabelas erradas ou deixando passar uma tabela relacionada. Mas não era apenas o RAG. Toda a base de código da AIDA havia sido feita na base da gambiarra em vez de engenharia de verdade, usando a fase inicial do "vibe coding". Venho dizendo isso há mais de uma década, e hoje é mais verdade do que nunca: se a sua base de código está te segurando, jogue tudo fora e comece do zero. Enfrentar as dores de crescimento da v1 ensina exatamente o que simplificar na v2. E enquanto reescritas costumavam exigir meses de esforço e capital político, hoje você consegue iterar por três arquiteturas limpas antes do almoço. Depois de [comparar as três principais bibliotecas de agentes em Go](https://danicat.dev/pt-br/posts/20260825-gemini-for-go-developers-part-3-building-agents/) (o [GenAI SDK](https://pkg.go.dev/google.golang.org/genai), o [Genkit](https://genkit.dev) e o [ADK](https://adk.dev)), decidi reconstruir a AIDA do zero em Go usando o **Genkit**. Migrar de Python e RAG para Genkit Go me permitiu: 1. **Compilar a AIDA em um único binário Go autocontido** que roda tanto como ferramenta local de CLI quanto como serviço HTTP no Cloud Run. 2. **Substituir o pipeline de RAG em SQLite por Agent Skills sob demanda**, entregando ao modelo o catálogo completo de tabelas do `osquery` para cada sistema operacional sem depender de buscas difusas. 3. **Garantir segurança de tipos em tempo de compilação** desde as entradas do prompt até as saídas estruturadas usando `dotprompt` e structs Go. 4. **Evoluir de um fluxo único para delegação multiagente com estado**, isolando a telemetria ruidosa do sistema das pesquisas na web. Vamos ver como cada parte dessa nova arquitetura funciona na prática. ## Configurando seu agente de programação e fluxo de trabalho local Antes de escrever qualquer código de aplicação, precisamos de duas coisas: a CLI local `genkit` para a interface de desenvolvimento e utilitários de linha de comando, e as Agent Skills que ensinam ao seu agente de programação como o Genkit Go funciona. ### Genkit CLI e Dev UI Para instalar a CLI `genkit`, execute o script oficial de instalação: ```sh curl -sL cli.genkit.dev | bash ``` A CLI `genkit` é a espinha dorsal do desenvolvimento local no Genkit Go. Se você executar seu programa diretamente com `go run .`, ele funciona como um binário Go normal, mas ao prefixar seu comando de execução com `genkit start --` você terá acesso à **Genkit Developer UI** (por padrão em `http://localhost:4000`), onde pode inspecionar visualmente e testar os componentes do seu programa Genkit de forma individual: ```sh genkit start -- go run . ``` Na Dev UI, você pode testar fluxos, prompts e agentes registrados de forma interativa, além de inspecionar traces de prompts, chamadas de ferramentas, uso de tokens e latência. Além da Dev UI, a CLI `genkit` também fornece utilitários de linha de comando para executar fluxos, visualizar traces e navegar pela documentação. Esse último recurso é particularmente útil para evitar alucinações do modelo durante a fase de implementação. Aqui estão alguns exemplos: ```sh # Run a specific flow once from the terminal and exit (--stream or --wait optional) genkit flow:run diagnose '{"userQuestion": "how is my disk usage?"}' --non-interactive -- go run . # Browse and read the embedded Genkit Go documentation offline genkit docs:list go genkit docs:read go/middleware.md ``` ### Skills recomendadas O padrão ouro para SDKs hoje em dia é distribuí-los com Agent Skills oficiais, e com o Genkit não é diferente. Se você está desenvolvendo um programa em Genkit Go, esta é a skill que você procura: - [`developing-genkit-go`](https://github.com/genkit-ai/skills): a skill oficial do Genkit Go cobrindo fluxos, `dotprompt`, middlewares nativos e agentes experimentais. Como estamos desenvolvendo com modelos Gemini, também é útil instalar a skill `gemini-api-dev`: - [`gemini-api-dev`](https://github.com/google-gemini/gemini-skills): a skill oficial do Gemini para IDs de modelos atuais e configuração de raciocínio (*thinking*). Você pode instalá-las no seu workspace usando a [CLI `skills`](https://github.com/vercel-labs/skills) da Vercel: ```sh npx skills add genkit-ai/skills --skill developing-genkit-go npx skills add google-gemini/gemini-skills --skill gemini-api-dev ``` Ou registrar os catálogos com o [`kungfu`](https://github.com/danicat/kungfu) para carregamento just-in-time (JIT): ```sh kungfu catalog add genkit-ai/skills kungfu catalog add google-gemini/gemini-skills ``` Se você preferir usar um servidor MCP em vez de skills, a CLI também inclui um [servidor MCP](https://genkit.dev/docs/go/mcp-server/) (`genkit mcp`). Ultimamente, tenho preferido usar mais skills, mas os MCPs ainda têm valor para certos casos de uso específicos (como consulta de documentação). ## Conceitos principais O Genkit é um framework open-source para aplicações de IA generativa criado originalmente pela equipe do Firebase antes de crescer e se tornar um projeto próprio. Embora sua linguagem original seja JS, o Genkit Go parece surpreendentemente idiomático para desenvolvedores Go experientes, mesmo que ainda tenha algumas arestas para aparar aqui e ali. Um programa típico em Genkit não será muito diferente de um serviço web tradicional, graças a adaptadores como `genkit.Handler` e construções familiares como middlewares e fluxos e prompts fortemente tipados. ### Fluxos (*Flows*) A unidade fundamental no Genkit é um **fluxo** (*flow*): uma função fortemente tipada que encapsula chamadas de IA generativa junto com qualquer lógica de pré ou pós-processamento. Vamos começar a AIDA como um fluxo de diagnóstico de turno único equipado com uma ferramenta para consultar o sistema operacional via `osqueryi`: ```go type AIDARequest struct { OS string `json:"os,omitempty" jsonschema_description:"The target operating system (defaults to runtime.GOOS)"` UserQuestion string `json:"userQuestion" jsonschema_description:"The diagnostic question to investigate"` } type AIDAResponse struct { Message string `json:"message" jsonschema_description:"The diagnostic findings and recommendations"` } type OsqueryInput struct { Query string `json:"query" jsonschema_description:"The SQL query to execute in osquery."` } g := genkit.Init(ctx, genkit.WithPlugins(&googlegenai.VertexAI{}), genkit.WithDefaultModel("vertexai/gemini-3.8-flash"), ) runOsquery := genkit.DefineTool(g, "runOsquery", "Runs a system inspection query using osquery.", func(ctx *ai.ToolContext, in OsqueryInput) (string, error) { out, err := exec.CommandContext(ctx, "osqueryi", "--json", in.Query).CombinedOutput() if err != nil { return "", fmt.Errorf("osqueryi failed: %w: %s", err, out) } return string(out), nil }, ) diagnoseFlow := genkit.DefineFlow( g, "diagnose", func(ctx context.Context, req AIDARequest) (AIDAResponse, error) { if req.OS == "" { req.OS = runtime.GOOS } resp, err := genkit.Generate(ctx, g, ai.WithSystem(fmt.Sprintf("You are the Emergency Diagnostic Agent for diagnosing %s operating system failures. Your name is AIDA.", req.OS)), ai.WithPrompt(req.UserQuestion), ai.WithTools(runOsquery), ) if err != nil { return AIDAResponse{}, fmt.Errorf("diagnose flow failed: %w", err) } return AIDAResponse{Message: resp.Text()}, nil }, ) ``` `genkit.DefineTool` registra a ferramenta `runOsquery` usando a struct `OsqueryInput` para gerar seu JSON schema, enquanto `genkit.DefineFlow` registra o fluxo pelo nome com uma função de callback. Se você observar a assinatura do callback, ela é muito parecida com um handler HTTP tipado — recebe um `context.Context` e uma struct de requisição fortemente tipada, e retorna uma resposta tipada ao lado de um `error` padrão do Go. Repare na variável `g` passada como primeiro argumento para `DefineTool`, `DefineFlow` e `Generate`. `g` é uma instância de `*genkit.Genkit`, que atua como o registro central de todos os fluxos, prompts, ferramentas, modelos e middlewares do seu programa. Você inicializa `g` uma única vez na função `main` com `genkit.Init` e a passa explicitamente. Vou ser sincera: não sou muito fã de `genkit.WithDefaultModel`, pois prefiro que o código seja explícito, mas ele ajuda a reduzir um pouco de código repetitivo (*boilerplate*) se você estiver definindo muitos fluxos. Veremos uma opção melhor do que fixar modelos diretamente no código dos fluxos quando falarmos sobre prompts. `genkit.Init` configura tanto o registro em tempo de execução quanto a stack de observabilidade. Cada chamada de modelo e ferramenta dentro de um fluxo é registrada automaticamente como um span filho dentro do trace do fluxo. Se o seu fluxo também realizar pré ou pós-processamento customizado — como consultar um banco de dados ou chamar uma API externa —, encapsular essa lógica em `genkit.Run` registra a operação como uma subetapa nomeada própria na cascata do trace. Para fins de desenvolvimento, você pode executar um fluxo via CLI ou na Dev UI. Em produção, você pode invocar fluxos como uma chamada de função Go normal (por exemplo, `myFlow.Run`) ou expô-los como um endpoint HTTP. Transformar um fluxo em um handler HTTP leva apenas uma linha com `genkit.Handler`: ```go mux := http.NewServeMux() mux.HandleFunc("POST /diagnose", genkit.Handler(diagnoseFlow)) port := os.Getenv("PORT") if port == "" { port = "3400" } // Note: "server" requires the server plugin: // import "github.com/firebase/genkit/go/plugins/server" log.Fatal(server.Start(ctx, "0.0.0.0:"+port, mux)) ``` Se você quiser expor todos os fluxos registrados na sua aplicação de uma só vez, pode iterar sobre `genkit.ListFlows(g)`: ```go mux := http.NewServeMux() for _, flow := range genkit.ListFlows(g) { mux.HandleFunc("POST /"+flow.Name(), genkit.Handler(flow)) } log.Fatal(server.Start(ctx, "0.0.0.0:"+port, mux)) ``` Vale lembrar que `genkit.Handler` não realiza nenhuma autenticação por conta própria, portanto proteja endpoints públicos com o seu middleware de autenticação padrão do `net/http`. ### Prompts Fixar prompts e configurações de modelo diretamente no código do fluxo funciona para projetos pequenos, mas pode ficar difícil de manter conforme o projeto cresce. Mesmo que os agentes de programação façam a maior parte do trabalho pesado hoje em dia, não devemos jogar fora anos de boas práticas de engenharia, e a organização de código é uma delas. O Genkit oferece suporte a templates de prompt usando um formato chamado [dotprompt](https://genkit.dev/docs/go/dotprompt/) (arquivos `*.prompt`). Os prompts em `dotprompt` podem conter mais do que apenas as instruções para o modelo; por exemplo, podem incluir metadados para selecionar e configurar o modelo, definições de schema, instruções de sistema e histórico de conversa. Extrair o prompt, as ferramentas e a configuração de modelo da AIDA de dentro do `diagnoseFlow` para o arquivo `prompts/aida.prompt` fica assim: ```markdown --- model: vertexai/gemini-3.8-flash tools: - runOsquery config: thinkingConfig: thinkingLevel: low input: schema: os: string userQuestion: string output: format: json schema: message: string --- {{role "system"}} You are the Emergency Diagnostic Agent for diagnosing {{os}} operating system failures. Your name is AIDA. {{role "user"}} {{userQuestion}} ``` Por padrão, `genkit.Init` carrega automaticamente os arquivos `.prompt` do diretório `./prompts` no seu diretório de trabalho. Se você preferir distribuir seu agente como um único binário, pode embutir o diretório `prompts` diretamente no binário com `//go:embed` e passá-lo para `genkit.WithPromptFS`: ```go import "embed" //go:embed prompts/* var promptsFS embed.FS func main() { g := genkit.Init(context.Background(), genkit.WithPromptFS(promptsFS)) // ... } ``` Os prompts ficam acessíveis no código por meio do método `genkit.LookupPrompt` e são invocados com `myPrompt.Execute`: ```go aida := genkit.LookupPrompt(g, "aida") resp, err := aida.Execute(ctx, ai.WithInput(map[string]any{ "os": runtime.GOOS, "userQuestion": "how is my disk usage?", })) ``` Se você programa em Go há algum tempo, aquele `map[string]any` provavelmente te incomoda tanto quanto me incomoda. Felizmente, o Genkit nos permite vincular as structs `AIDARequest` e `AIDAResponse` que definimos anteriormente diretamente ao `aida.prompt` usando **data prompts** (`genkit.LookupDataPrompt`) e `genkit.DefineSchemaFor`: ```go // In prompts/aida.prompt, reference the Go types by name: // // input: // schema: AIDARequest // output: // schema: AIDAResponse // // Register both schemas with Genkit so the prompt file can resolve them: genkit.DefineSchemaFor[AIDARequest](g) genkit.DefineSchemaFor[AIDAResponse](g) aida := genkit.LookupDataPrompt[AIDARequest, *AIDAResponse](g, "aida") resp, _, err := aida.Execute(ctx, AIDARequest{ OS: runtime.GOOS, UserQuestion: "how is my disk usage?", }) // resp is of type *AIDAResponse // Note: The second return value is the raw *ai.ModelResponse ``` Com `DefineSchemaFor` e `LookupDataPrompt`, você define seu schema uma única vez em Go e mantém total segurança de tipos em tempo de compilação, desde a entrada até a saída processada. ### Plugins Você estende o Genkit com pacotes sob o namespace `plugins/`. Embora o caso mais comum para um plugin seja registrar uma família de modelos, também temos plugins para capacidades mais amplas, como `server` e `middleware`. O `server` é um caso especial no sentido de que não precisa ser registrado para ser útil, mas a maioria dos plugins será inicializada na chamada `genkit.Init`: ```go import ( "github.com/firebase/genkit/go/genkit" "github.com/firebase/genkit/go/plugins/googlegenai" "github.com/firebase/genkit/go/plugins/middleware" ) g := genkit.Init(ctx, genkit.WithPlugins( &googlegenai.VertexAI{}, // Gemini via Vertex AI &middleware.Middleware{}, // Registers Retry, Fallback, Skills, etc. ), ) ``` O conceito de middleware deve ser familiar para a maioria dos desenvolvedores Go, mas é importante destacar esse plugin, pois ele é a porta de entrada para recursos críticos como fallbacks, novas tentativas (*retries*) e Agent Skills. Vamos analisá-lo mais de perto a seguir. ## Substituindo o SQLite RAG pelo middleware de Agent Skills A AIDA já consegue executar comandos `osquery` por meio da ferramenta `runOsquery` que definimos antes, mas ela ainda depende dos pesos gerais de treinamento do modelo para adivinhar quais tabelas e colunas existem em cada sistema operacional (`darwin`, `linux` e `windows`). Na versão legada em Python da AIDA, resolvi isso com um banco local de RAG em SQLite onde cada chunk continha a definição de uma tabela. Embora o conjunto de dados fosse pequeno e estático, a abordagem com RAG era trabalhosa de montar e tinha uma falha sutil: o `gemini-2.5-flash` precisava adivinhar primeiro o que buscar e, quando a consulta de RAG saía um pouco fora do alvo, ele trazia as tabelas erradas ou deixava passar uma tabela relacionada completamente. Na reescrita em Go, apaguei o pipeline de RAG e o substituí por [Agent Skills](https://agentskills.io). Apontando o Antigravity para o [repositório do osquery](https://github.com/osquery/osquery), gerei três skills específicas por sistema operacional (`darwin`, `linux` e `windows`) na pasta `./skills`, cada uma contendo o catálogo completo de tabelas e schemas daquela plataforma. Em vez de torcer para uma busca vetorial retornar a tabela certa, a AIDA simplesmente ativa a skill do sistema operacional atual e carrega a referência completa de schemas sob demanda. O Genkit anexa middlewares às chamadas de geração usando `ai.WithUse`, e o pacote `plugins/middleware` fornece implementações nativas para retentativas, fallbacks de modelo e [Agent Skills](https://agentskills.io): ```go resp, err := genkit.Generate(ctx, g, ai.WithSystem(fmt.Sprintf("You are AIDA, a %s operating system diagnostic assistant.", runtime.GOOS)), ai.WithPrompt("My machine is running out of file descriptors. How do I inspect open files with osquery?"), ai.WithTools(runOsquery), ai.WithUse( &middleware.Retry{MaxRetries: 3, InitialDelayMs: 1000, BackoffFactor: 2}, &middleware.Fallback{ Models: []ai.ModelRef{ googlegenai.ModelRef("vertexai/gemini-3.5-flash-lite", nil), }, }, &middleware.Skills{ SkillPaths: []string{"./skills"}, }, ), ) ``` Assim como um middleware HTTP, `ai.WithUse` compõe os itens na ordem declarada: colocar `Retry` antes de `Fallback` tenta novamente o modelo principal antes de recorrer ao modelo secundário. Quando você aponta `&middleware.Skills{}` para `./skills`, o Genkit examina cada subdiretório, injeta as descrições das skills no prompt de sistema e registra uma ferramenta `use_skill` para que a AIDA possa carregar o schema do sistema operacional e os padrões de consulta corretos sob demanda. Vale notar que, diferentemente do `dotprompt`, no momento o middleware `Skills` não suporta sistemas de arquivos embutidos (*embedded filesystems*), então você ainda precisa incluir um diretório de skills junto com o seu deploy. Como registramos `&middleware.Middleware{}` durante o `genkit.Init`, também podemos declarar esses middlewares diretamente dentro de `prompts/aida.prompt`: ```yaml --- model: vertexai/gemini-3.8-flash tools: - runOsquery use: - name: genkit-middleware/retry config: maxRetries: 2 - genkit-middleware/skills --- ``` Para casos de uso avançados, você também pode escrever middlewares customizados implementando a interface `ai.Middleware` para interceptar e encapsular chamadas de geração, modelo ou ferramenta com a sua própria lógica. ## A Developer UI Você encontra o código completo até este ponto no repositório complementar em [`github.com/danicat/gemini-for-go-developers/part-4/01-flow`](https://github.com/danicat/gemini-for-go-developers/tree/main/part-4/01-flow). Para testar a AIDA na Genkit Developer UI, inicie a aplicação a partir desse diretório com `genkit start`: ```sh export GOOGLE_CLOUD_PROJECT=your-project-id export GOOGLE_CLOUD_LOCATION=global # required for Gemini 3.x genkit start -- go run . ``` Abra `http://localhost:4000`, selecione o fluxo **diagnose** na barra lateral e execute-o com o seguinte payload de teste: ```json { "userQuestion": "what is my battery status?" } ``` Aqui está a saída desse prompt na minha máquina (observe o trace no painel direito mostrando que a skill foi ativada): ![Genkit Developer UI executando o fluxo diagnose](https://danicat.dev/pt-br/posts/building-agentic-backends-with-genkit-go/image-1.png) ## Agentes com estado (experimental) Normalmente prefiro manter distância de recursos experimentais, mas este é uma exceção. Embora seja perfeitamente possível implementar agentes baseados apenas em fluxos, desenvolver padrões mais complexos — como chatbots, agentes seriais e agentes paralelos — exige um certo esforço para construir os padrões certos usando goroutines, sincronização, tratamento de erros e afins. A introdução da abstração de agente simplifica esse processo ao nos fornecer uma API muito mais ergonômica, focada no aspecto declarativo da criação de agentes. ### Definindo um agente Como a API ainda é experimental, você precisa habilitá-la com `genkit.WithExperimental()` ao inicializar o Genkit. Veja como transformamos a AIDA em uma agente de terminal multi-turno com estado, apoiada por um armazenamento de sessão (*session store*): ```go import ( aix "github.com/firebase/genkit/go/ai/exp" "github.com/firebase/genkit/go/ai/exp/localstore" genkitx "github.com/firebase/genkit/go/genkit/exp" ) g := genkit.Init(ctx, genkit.WithExperimental(), genkit.WithPlugins(&googlegenai.VertexAI{}, &middleware.Middleware{}), ) store := localstore.NewInMemorySessionStore[struct{}]() aida := genkitx.DefineAgent(g, "aida", aix.InlinePrompt{ ai.WithModelName("vertexai/gemini-3.8-flash"), ai.WithSystem(fmt.Sprintf("You are AIDA, a %s operating system diagnostic assistant. Use runOsquery to inspect the host.", runtime.GOOS)), ai.WithTools(runOsquery), ai.WithUse(&middleware.Skills{SkillPaths: []string{"./skills"}}), }, aix.WithSessionStore(store), ) // Interactive terminal session maintaining state across turns scanner := bufio.NewScanner(os.Stdin) var sessionID string fmt.Print("AIDA> ") for scanner.Scan() { input := strings.TrimSpace(scanner.Text()) if input == "" || input == "exit" { break } var opts []aix.InvocationOption[struct{}] if sessionID != "" { opts = append(opts, aix.WithSessionID[struct{}](sessionID)) } out, err := aida.RunText(ctx, input, opts...) if err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) fmt.Print("AIDA> ") continue } sessionID = out.SessionID fmt.Printf("\n%s\n\nAIDA> ", out.Message.Text()) } ``` Armazenar `out.SessionID` e passá-lo nos turnos seguintes preserva o histórico da conversa ao longo da sessão. O parâmetro de tipo `[struct{}]` simplesmente informa ao Genkit que queremos apenas persistir o histórico de mensagens sem anexar nenhum estado de sessão customizado. As mesmas técnicas usadas para melhorar o código em torno dos fluxos podem ser aplicadas aos agentes. Para evitar fixar os prompts no código, você pode usar `genkitx.DefinePromptAgent` para vincular `prompts/aida.prompt` diretamente ao agente. Para expor a AIDA via HTTP de modo que um frontend remoto possa se conectar a ela, `genkitx.AllAgentRoutes` monta endpoints de turno, snapshot e cancelamento (*abort*) em roteadores Go padrão: ```go mux := http.NewServeMux() for _, route := range genkitx.AllAgentRoutes(g) { mux.HandleFunc(route.Pattern(), route.Handler()) } ``` Você encontra a implementação completa do agente com estado no repositório complementar em [`github.com/danicat/gemini-for-go-developers/part-4/02-agent`](https://github.com/danicat/gemini-for-go-developers/tree/main/part-4/02-agent). ### Delegação multiagente Existem casos em que você precisará de mais de um agente para realizar uma tarefa. Os benefícios de uma arquitetura multiagente incluem a separação de responsabilidades — para melhorar a atenção do agente e reduzir a degradação de contexto (*context rot*) — e a paralelização — para melhorar os tempos de resposta. Usando o middleware experimental `middlewarex.Agents` do Genkit, podemos dividir o trabalho investigativo da AIDA entre dois especialistas, mantendo `aida` como a persona coordenadora principal: - **`inspector`**: Foca exclusivamente em consultar a telemetria do host via `runOsquery` e as skills de `osquery` específicas do sistema operacional. - **`researcher`**: Usa o aterramento com a Busca do Google (*Google Search grounding*) para pesquisar processos desconhecidos, binários, mensagens de erro e CVEs na web sem inflar o histórico da conversa principal da AIDA. ```go // requires: // import ( // "google.golang.org/genai" // middlewarex "github.com/firebase/genkit/go/plugins/middleware/exp" // ) inspector := genkitx.DefineAgent(g, "inspector", aix.InlinePrompt{ ai.WithModelName("vertexai/gemini-3.8-flash"), ai.WithSystem(fmt.Sprintf("Inspect %s system telemetry using osquery and report diagnostic findings.", runtime.GOOS)), ai.WithTools(runOsquery), ai.WithUse( &middleware.Retry{MaxRetries: 5}, &middleware.Skills{SkillPaths: []string{"./skills"}}, ), }, aix.WithDescription[struct{}]("Inspects host metrics, running processes, and system configuration using osquery."), ) researcher := genkitx.DefineAgent(g, "researcher", aix.InlinePrompt{ ai.WithModelName("vertexai/gemini-3.8-flash"), ai.WithSystem("Research unfamiliar processes, binaries, error messages, and CVEs using Google Search and report concise findings."), ai.WithConfig(&genai.GenerateContentConfig{ Tools: []*genai.Tool{ {GoogleSearch: &genai.GoogleSearch{}}, }, }), }, aix.WithDescription[struct{}]("Searches the web for information on unfamiliar processes, binaries, error codes, and CVEs."), ) aida := genkitx.DefineAgent(g, "aida", aix.InlinePrompt{ ai.WithModelName("vertexai/gemini-3.8-flash"), ai.WithSystem(fmt.Sprintf("You are AIDA, a %s operating system diagnostic assistant. Delegate host inspection to inspector and web research to researcher, then synthesize findings for the user.", runtime.GOOS)), ai.WithUse( &middleware.Retry{MaxRetries: 5}, &middlewarex.Agents{ Agents: []aix.AgentRef{inspector.Ref(), researcher.Ref()}, MaxDelegations: 5, }, ), }, aix.WithSessionStore(store), ) ``` A delegação é executada de forma síncrona por padrão, embora você possa definir `Async: true` em `middlewarex.Agents` caso queira que a AIDA dispare tarefas especializadas de longa duração em segundo plano e reúna os resultados entre os turnos. Um ponto importante para ter em mente é o trade-off de latência. Quando cronometrei exatamente a mesma consulta `"what is my battery status?"` nas duas versões (com `thinkingLevel` definido como `low`), a versão com um único agente (`02-agent`) terminou em **9,56s** ao longo de 3 chamadas de LLM, enquanto esta versão multiagente (`03-multi-agent`) levou **17,02s** ao longo de 5 chamadas de LLM. A latência por chamada é basicamente idêntica (~3,3s), então os ~7,5 segundos extras vêm puramente das duas viagens de ida e volta adicionais da coordenadora quando `aida` delega para `inspector` e depois resume o relatório dele. Assim como na programação paralela, o custo de coordenação para um *N* pequeno pode deixar uma tarefa simples mais lenta. Para uma consulta simples de exemplo, um único agente é mais rápido; em um sistema do mundo real, contudo, dividir um "superagente" em subagentes focados geralmente compensa essas viagens extras para isolar saídas ruidosas de ferramentas, otimizar o uso de contexto e melhorar a qualidade geral das respostas. Você encontra o exemplo multiagente completo no repositório complementar em [`github.com/danicat/gemini-for-go-developers/part-4/03-multi-agent`](https://github.com/danicat/gemini-for-go-developers/tree/main/part-4/03-multi-agent). ## Fazendo o deploy em produção ### Runtimes Como uma aplicação Genkit Go compila para um binário Go padrão usando `net/http`, não há dependência (*lock-in*) de nenhum runtime proprietário. Isso significa que praticamente qualquer plataforma que você já usa para hospedar seus servidores HTTP serve para aplicações Genkit. Minha primeira opção é sempre um runtime de containers serverless. No Google, uso principalmente o **Google Cloud Run**, escolhendo plataformas mais complexas (como o **Google Kubernetes Engine (GKE)** ou o **Gemini Enterprise**) apenas quando esbarro em algum bloqueio crítico que exige um recurso que o Cloud Run não me oferece. Também vale dizer que, no passado, esse tipo de decisão costumava ser mais importante do que é hoje. Mudar de plataforma (*replatforming*) dava muito trabalho, então as pessoas tinham a tendência de querer "acertar de primeira" logo no início. Com agentes de programação, conseguimos adaptar uma aplicação para uma nova plataforma em questão de dias (se não horas), então não há mais desculpas para adicionar complexidade cedo demais a um projeto. Essa é a teoria. Na prática, ao longo do último ano não encontrei nenhum caso que eu não conseguisse resolver com o Cloud Run. Ele é simplesmente conveniente demais! A principal exceção é quando você está construindo fortemente em torno do [ADK](https://adk.dev) e quer persistência de sessão gerenciada e conectores corporativos prontos para uso, onde o Gemini Enterprise Agent Platform (que cobrimos em [Construindo Agentes de IA em Go](https://danicat.dev/pt-br/posts/20260825-gemini-for-go-developers-part-3-building-agents/)) oferece fortes sinergias nativas. Normalmente, os agentes — especialmente os baseados em Gemini, como o Antigravity — não precisam de ajuda para fazer deploy no Cloud Run. Mas, se o seu agente estiver de mau humor, você sempre pode instalar a skill do Cloud Run disponível em [github.com/google/skills](https://github.com/google/skills/tree/main/skills/cloud/cloud-run-basics) para dar um empurrãozinho nele. ### Observabilidade Durante o desenvolvimento local, o comando `genkit start` grava traces completos de execução e logs estruturados em `.genkit/traces` (que você deve adicionar ao `.gitignore`) para que você possa inspecioná-los na Dev UI. Em produção, você quer ter essa mesma visibilidade sobre o uso de tokens, latência e traces passo a passo no seu backend de monitoramento. Como o Genkit é construído sobre o [OpenTelemetry](https://opentelemetry.io/), você tem duas opções principais: se estiver fazendo deploy no Google Cloud, o plugin `googlecloud` (ou o plugin `firebase`, que executa o mesmo exportador por baixo dos panos) envia tudo para o Cloud Logging, Cloud Trace e Cloud Monitoring. Se a sua equipe usa outro backend como Datadog, Grafana ou Honeycomb, você pode anexar um exportador OTLP padrão diretamente ao tracer provider do Genkit. Para um deploy típico no Cloud Run, habilite o exportador do Google Cloud antes de chamar `genkit.Init`: ```go import "github.com/firebase/genkit/go/plugins/googlecloud" func main() { ctx := context.Background() googlecloud.EnableGoogleCloudTelemetry(&googlecloud.GoogleCloudTelemetryOptions{ // Raw prompts and responses are logged by default; disable this for sensitive data: DisableLoggingInputAndOutput: true, // Set to true if you want to test Cloud export locally under `genkit start`: ForceDevExport: false, }) g := genkit.Init(ctx, // if you are deploying to GCP, auth via Gemini Enterprise (aka Vertex AI) is preferred genkit.WithPlugins(&googlegenai.VertexAI{}), ) // ... } ``` Se você preferir um backend OTLP, registre um processador de spans padrão do OpenTelemetry antes de `genkit.Init` e use `defer` na sua função de encerramento (*shutdown*): ```go import ( "context" "log" "github.com/firebase/genkit/go/core/tracing" "go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc" sdktrace "go.opentelemetry.io/otel/sdk/trace" ) func registerOTLP(ctx context.Context) func(context.Context) error { // Configured via standard OTEL_EXPORTER_OTLP_* environment variables. exp, err := otlptracegrpc.New(ctx) if err != nil { log.Fatalf("failed to build OTLP exporter: %v", err) } tp := tracing.TracerProvider() tp.RegisterSpanProcessor(sdktrace.NewBatchSpanProcessor(exp)) return tp.Shutdown } ``` ## Conclusão Reconstruir a AIDA do zero é um ótimo lembrete de quão rápido a engenharia de IA amadureceu nos últimos doze meses. O que antes exigia um pipeline de RAG customizado em SQLite, um modelo de embedding local e centenas de linhas de código Python frágil (uma crítica ao meu próprio vibe coding inicial, e não ao Python em si) agora é compilado em um único binário Go fortemente tipado, impulsionado pelo Genkit e por Agent Skills sob demanda. Migrar para o Genkit Go trouxe para a AIDA validação de schemas em tempo de compilação com `dotprompt`, retentativas nativas e injeção de skills por middleware, além de uma delegação multiagente limpa, mantendo a inicialização rápida e a simplicidade de deploy de um serviço HTTP padrão em Go. Combinado com um modelo rápido como o Gemini 3.8 Flash, a diferença de agilidade em comparação com a versão legada é da água para o vinho. Neste artigo focamos no runtime principal e na arquitetura de backend da AIDA, reservando o frontend (usando A2UI e design de interação generativa) para um artigo de continuação. Se você planeja construir agentes em Go, ou se tem um protótipo inicial com RAG começando a mostrar a idade, recomendo fortemente dar uma chance ao Genkit. --- # O Guia Pragmático para Agent Skills - **Markdown URL**: https://danicat.dev/pt-br/posts/the-pragmatic-guide-to-agent-skills/index.md - **HTML URL**: https://danicat.dev/pt-br/posts/the-pragmatic-guide-to-agent-skills/ - **Date**: 2026-08-29 - **Categories**: Agentic Coding - **Tags**: agent-skills, antigravity, gemini-cli, vibe-coding - **Summary**: Um guia pragmático para criar Agent Skills customizadas para coding agents: explore casos de uso práticos em documentação, automação de processos e traços de personalidade. Pode parecer difícil de acreditar, mas o padrão [**Agent Skills**](https://agentskills.io) não tem sequer um ano de vida. O [artigo original da Anthropic](https://www.anthropic.com/engineering/equipping-agents-for-the-real-world-with-agent-skills) foi publicado em outubro de 2025, apresentando inicialmente as Agent Skills como uma extensão do Claude Code antes de se consolidar como um padrão aberto em dezembro de 2025. O padrão provou ser tão útil que, em pouquíssimo tempo, foi adotado por todos os principais coding agents e frameworks de desenvolvimento de agentes para equipar modelos de IA com instruções modulares, scripts determinísticos e fluxos especializados de domínio. Avançando para os dias de hoje, a maioria dos desenvolvedores já conhece as skills e as vantagens do modelo de progressive disclosure, mas será que estamos usando e criando skills de forma realmente eficaz? Neste artigo, vamos explorar os principais desafios ao lidar com skills, desde a descoberta e proveniência até o desenvolvimento e otimização. Todas as skills e exemplos mencionados neste texto foram testados usando o Antigravity CLI e o Gemini 3.7 Flash, mas incentivo você a experimentar mesmo se utilizar outro harness e/ou modelo. Sem mais delongas, vamos mergulhar fundo no universo das Agent Skills! ## Criar skills vs consumir skills O eterno dilema da engenharia de software: devo construir a minha própria solução ou adotar algo pronto? Existem várias abordagens para esse problema, mas vou compartilhar minha visão pragmática: ```goat +-----------------------------------------+ | Is this process specific to your repo | | or codebase? | +-----------------------------------------+ | | Yes | | No | v | +-----------------------+ | | Do you know a skill |<-------------------+ | | that does the job? | | | +-----------------------+ | | | | | | Yes | | No | | v v | | +--------------------+ +------------------+ | | | Passes security & | | Spent too much | | | | integrity checks? | | time looking? | | | +--------------------+ +------------------+ | | | | | | | | Yes | No | Yes | No | | | | | | v | | | | | +---------------+ | | | | | | Search for | | | | | | | a skill | | | | | | +---------------+ | | | | | | | | | v v +------------+ | | +------------------+ | | | Build your own | +--------------------->| skill | | +------------------+ | | v v +-----------------------------------+ | Profit! | +-----------------------------------+ ``` Admito que o algoritmo é um pouco caótico, mas o ponto central é: a menos que você tenha uma fonte autoritativa para a skill, ou confiança absoluta de que não está se expondo a um vetor de ataque, é melhor construir a sua própria. O esforço para criar skills é muito baixo, então há pouquíssimas circunstâncias em que eu desaconselharia essa criação. A principal seria a falta de conhecimento no domínio para avaliar a qualidade técnica da skill, já que isso cria um grande risco de alimentar uma câmara de eco de comportamentos ruins. Se você sabe o que está fazendo, construir skills é o maior hack para desbloquear a produtividade com coding agents — e é exatamente por isso que acredito que todo desenvolvedor sério deveria dominar a habilidade de criar skills (com o perdão do trocadilho). ## Dominando a habilidade de criar skills O segredo para criar skills úteis reside em observar os padrões do agente. Você pode não conseguir conceber uma skill logo na primeira sessão de código, mas quanto mais usar agentes e observar seu comportamento, mais notará lacunas de conhecimento e de comportamento que podem ser preenchidas com skills customizadas. Isso ficará mais claro com alguns exemplos práticos, mas antes de falar sobre eles, vamos dar uma olhada nos diferentes tipos de skills que podemos criar: ### Nível 1: Documentação Uma skill que explica uma tecnologia específica, ampliando ou substituindo o conhecimento nativo do modelo. É extremamente comum, por exemplo, que modelos recomendem bibliotecas ou SDKs obsoletos, ou usem padrões ultrapassados de código, simplesmente porque eram prevalentes nos seus dados de treinamento. Usar skills para atualizar o conhecimento do modelo sobre um software é um dos usos mais básicos dessa tecnologia. O formato típico de uma skill de documentação é um único arquivo `SKILL.md` com o conhecimento atualizado, mostrando linhas de comando, snippets de código e URLs externas. Dependendo da complexidade do domínio, dividir o arquivo `SKILL.md` em arquivos separados de referência e/ou assets é ideal para maximizar o potencial do progressive disclosure. Crie esse tipo de skill quando notar seu agente usando padrões obsoletos, bibliotecas desatualizadas, versões antigas de SDKs ou quando ele gerar repetidamente implementações longe do ideal. A maioria das skills fornecidas pelos fabricantes se encaixa nessa categoria, como o repositório [Google Skills](https://github.com/google/skills), que inclui skills para a maioria dos produtos do Google. Um exemplo de skill de documentação que criei é a [ebitengineer](https://skills.danicat.dev/game-dev/ebitengineer/). Essa skill nasceu após eu notar problemas recorrentes enquanto criava jogos 2D com o Ebitengine, como a ordem incorreta de operações de matriz, modularização deficiente, falta de gerenciamento de estado, abuso de texto de debug em vez de fontes de produção e assim por diante. ### Nível 2: Processo Uma skill que estabelece um processo ou workflow específico, como [fazer um code review](https://danicat.dev/pt-br/posts/20260303-code-reviews-in-2026/), conduzir uma auditoria de segurança ou analisar métricas de performance. Esse tipo de skill costuma empacotar não apenas conhecimento, mas scripts customizados ou ferramentas de CLI para executar tarefas, poupando o modelo do esforço de construí-los do zero a cada vez que você precisa deles. Você deve criar skills de processo sempre que se pegar instruindo o modelo a fazer a mesma tarefa repetidamente, especialmente quando houver passos determinísticos que podem ser automatizados via script. Algumas skills de processo que utilizo com frequência fazem parte do conjunto de [analytics](https://skills.danicat.dev/analytics), usadas para coletar e analisar dados de redes sociais, Google Analytics e Google Search Console. Otimizar minha presença online é uma parte importante do meu trabalho (de pouco adianta produzir conteúdo que ninguém vê), então criei essas skills para automatizar as tarefas que antes eu fazia manualmente inspecionando o site de analytics de cada plataforma, um por um. Agora tenho uma "central de comando" alimentada por essas skills, economizando um tempo precioso que posso dedicar a produzir mais e melhores conteúdos. Olhe para o seu próprio processo. Quais são as tarefas que você executa repetidamente durante o seu dia de trabalho, semana ou mês? Crie skills para elas a fim de poupar tempo precioso, melhorar a qualidade do seu trabalho, ou ambos! ### Nível 3: Traços Uma skill de traço (trait) afeta a personalidade e/ou o modo de operação do agente. Uma skill famosa nessa categoria é a [`/grill-me`](https://github.com/mattpocock/skills), criada por Matt Pocock. Ficou tão popular que a maioria dos harnesses de agentes já inclui uma versão nativa pronta para uso. O propósito dessa skill é encorajar o agente a extrair conhecimento do usuário, preenchendo lacunas que, de outra forma, seriam adivinhadas ou, pior ainda, alucinadas. Algumas das minhas skills favoritas baseadas em traços são: [swarm-coding](https://skills.danicat.dev/agents/swarm-coding), [double-diamond](https://skills.danicat.dev/agents/double-diamond) e [uno-reverse](https://skills.danicat.dev/agents/uno-reverse). Swarm coding foi a primeira skill que escrevi para lidar com a orquestração de subagents. Escrevi sobre todo o processo de criação dela em [A Ascensão dos Subagentes](https://danicat.dev/pt-br/posts/20260722-the-rise-of-the-subagents/), então recomendo conferir esse artigo se você tiver curiosidade a respeito. Double diamond é a sucessora espiritual da swarm coding, mas inclui uma fase de concepção (inception) e de descoberta (discovery). Quem está familiarizado com metodologias ágeis reconhecerá essa técnica. Ela se baseia no princípio de fases sucessivas de divergência e convergência. Você diverge para explorar o espaço do problema e depois converge para uma solução. Por exemplo, ao planejar uma grande feature neste site, posso iniciar toda a exploração com: ```text /double-diamond I would like to plan phase 3 of this website. I want to improve the visualization of skill cards, add metrics, and a like/star button so we can rank skills by popularity. ``` Uno reverse é a minha própria abordagem para ter um agente com uma personalidade adversarial, com o único objetivo de quebrar a câmara de eco dos agentes que estão implementando o código. O agente adversarial criticará a implementação e buscará oportunidades para cortar excessos, funcionando como um contraponto contra o overengineering. Eu frequentemente o utilizo como uma checagem de sanidade durante revisões de arquitetura: ```text Please run a /double-diamond process to refine ADR-0002. At the review phase, use an /uno-reverse agent to antagonise the proposal and present the results. ``` Como você pode ver, essas skills já são muito poderosas sozinhas, mas ficam ainda mais interessantes quando combinadas entre si. Por exemplo, eu me divirto muito usando `/grill-me` + `/double-diamond` + `/uno-reverse` no mesmo prompt. Isso faz o modelo me fazer perguntas esclarecedoras, partir para uma fase de pesquisa para encontrar diferentes implementações e, em seguida, criticá-las para chegar à solução ideal. Quanto à inspiração para criar essas skills, procure momentos da sua carreira em que você aprendeu uma habilidade (humana) que ajudou você ou seu time a melhorar a qualidade do trabalho. Especialmente desde o advento dos subagents, estamos vendo cada vez mais o uso de habilidades de gestão sendo aplicadas a IAs, e isso não é coincidência. A IA simplesmente se mostra tão pouco confiável quanto nós quando deixada por conta própria. ## O ciclo de melhoria contínua Criar skills é ótimo, mas você só vai realmente desbloquear todo o valor das skills se estiver melhorando-as continuamente. Meu processo é dar um tapa no visual e no conteúdo (um glow-up) das minhas skills quase toda vez que as uso, porque frequentemente descubro arestas, lacunas e bugs quando as utilizo em uma nova sessão. Outra desculpa para atualizá-las é quando um novo modelo é lançado, já que é esperado que o novo modelo seja mais capaz e você queira atualizar a skill para aproveitar o melhor de seus recursos. Faço isso com tanta frequência que criei a skill [`/skill-optimizer`](https://skills.danicat.dev/agents/skill-optimizer). Essa skill é baseada nas melhores práticas publicadas no [agentskills.io](https://agentskills.io), com um toque do meu próprio gosto. Ela garante que as skills tenham um bom modelo de progressive disclosure, usando um script de apoio para estimar a contagem de tokens e manter cada seção em um tamanho razoável. Ela também otimiza a descrição da skill focando em benefícios em vez de detalhes de implementação. Por exemplo, antes da otimização, minha skill [search-analytics](https://skills.danicat.dev/analytics/search-analytics) estava cheia de enrolação sobre como a busca funcionava usando o algoritmo X ou Y, mas isso importa muito pouco para o uso da skill e era um desperdício de espaço. A informação relevante nesse caso é como carregar e consultar os dados. Todo o resto é desperdício de tokens. Além de criar skills e otimizá-las por conta própria, também existe uma maneira automatizada de fazer isso: um conceito chamado **agent dreaming**, o processo de extrair lições de sessões anteriores dos agentes. No Antigravity, você pode pedir para ele "sonhar" usando o comando de barra `/learn`. Quando você inicia uma sessão de aprendizado, o agente explora o histórico da conversa em busca de padrões e procura maneiras de se aprimorar, oferecendo opções para atualizar arquivos de contexto, adicionar ou calibrar guardrails de configuração, ou gerar skills customizadas. Por exemplo, após uma longa maratona de debug, você poderia simplesmente rodar: ```text /learn look at our debugging sessions with Ebitengine matrix transforms and state management. Extract the recurring bugs and fixes into a new documentation skill with clear anti-patterns and examples. ``` ## Ativação: explícito é melhor do que implícito Passei mais tempo do que gostaria de admitir tentando calibrar descrições para garantir que as skills fossem sempre ativadas quando o modelo precisasse delas. Falhei em todas as minhas tentativas até agora. Você consegue chegar a um ponto em que os modelos as ativam na maioria das vezes, mas, na minha experiência, taxas de ativação perfeitas ainda são inalcançáveis hoje. É por isso que estou importando um velho adágio do Zen do Python e mudando a minha própria forma de trabalhar: explícito é melhor do que implícito. Em vez de esperar pela boa vontade do agente para invocar a skill que eu quero, eu digo exatamente o que quero invocado e quando. No Antigravity também temos o benefício do mapeamento automático de skills para comandos de barra, então quando digito `/swarm-coding`, ele invoca imediatamente a skill de swarm coding. A notação de barra transforma skills em slash commands quando elas são a primeira palavra em um prompt, mas essa notação também funciona no meio de prompts como dicas para o modelo de que o que está prefixado com barra pode ser o nome de uma skill. Por exemplo, você pode adicionar `/grill-me` no final do seu prompt e o agente disparará a skill `grill-me` também, mesmo que tecnicamente não seja um slash command. Em versões recentes do Antigravity CLI, notei inclusive que o harness ocasionalmente adiciona `/grill-me` automaticamente ao final dos meus prompts quando meus comandos são relativamente complexos ou ambíguos. ## O problema de skills demais Assim que você abraçar o mundo das skills, eventualmente chegará a esta situação. Por melhor que seja o modelo de progressive disclosure, você ainda sofrerá com o mesmo problema de inchaço de contexto (context bloat) que ocorre com servidores MCP se continuar instalando mais e mais skills. Existem duas maneiras de melhorar isso: uma que exige trabalho ativo da sua parte e outra que emprega um "nível mais alto de abstração". A primeira é simples: não instale skills globalmente, a menos que seja absolutamente necessário. As skills podem ser instaladas para o seu usuário (elas ficam na sua pasta home, geralmente em `~/.agents/skills`) ou em um workspace (nível de projeto, em `/.agents/skills`). Essa solução funciona em pequena e média escala, mas começa a trazer problemas se você tem muitos projetos com muitas skills neles. Como mantê-las todas atualizadas? Como garantir que uma melhoria em uma skill seja refletida em todos os outros projetos que usam essa skill? Além disso, a pasta `.agents` deve ser commitada no repositório ou não? Acho que a solução ideal para esse problema é uma combinação de centralização (ou federação) e rastreamento de proveniência. A primeira parte da solução é um catálogo de skills. Enquanto o compartilhamento típico de skills depende de git clones ou copiar e colar pastas, o padrão oficial de Agent Skills deixa a descoberta remota e a distribuição abertas para as ferramentas resolverem. Para solucionar isso, criei um catálogo manifesto online para `skills.danicat.dev` que expõe um índice pela web. Você pode inspecionar o catálogo manifesto em produção aqui: [https://skills.danicat.dev/catalog.json](https://skills.danicat.dev/catalog.json). Um catálogo de skills resolve o problema da descoberta de skills. A ideia é que você pode apontar seus agentes para o catálogo e eles saberão imediatamente quais skills estão disponíveis sem precisar instalá-las. Isso não resolve o problema do context bloat, no entanto, a menos que você tenha um procedimento adequado de "buscar skill" que evite carregar o catálogo inteiro toda vez que estiver procurando uma nova skill. O catálogo resolve uma das direções do fluxo de informação — do catálogo para a skill —, mas manter as skills atualizadas exige a direção oposta — da skill para o catálogo. É a isso que tenho chamado de "proveniência da skill": a fonte da skill. A especificação de Agent Skills não é prescritiva sobre proveniência, mas existem duas maneiras de gerenciar isso: 1) um gerenciador externo de skills que rastreia a origem de cada skill instalada, ou 2) adicionar informações do catálogo aos metadados da skill. Acredito que a segunda opção é mais elegante por ser "declarativa": não é preciso adivinhar qual mágica acontece nos bastidores; o que você vê é o que você tem. Este é um exemplo típico de frontmatter no meu catálogo de skills: ```yaml --- name: swarm-coding description: > Orchestrates multi-agent hierarchical swarms using a divide-and-conquer architecture for complex, multi-system, or orthogonal engineering initiatives (e.g., concurrent backend, frontend, database, QA). Manages hierarchical Lead Agents and Specialists, disjoint work allocations, and strict parent-child communication. Activate whenever the user mentions 'swarm', requests multi-agent team coordination, or needs context isolation across multiple technical domains. license: Apache-2.0 metadata: category: agents tags: "swarm, subagents, parallel, orchestration, strategy, complexity, coordination" author: Daniela Petruzalek (daniela@danicat.dev) version: "0.2.0" catalog: https://skills.danicat.dev --- ``` `name`, `description`, `license` e `metadata` são os campos padrão definidos pela especificação. Embora `metadata` tenha uma definição livre, decidi seguir a recomendação de `version` e `author` como campos padrão. Minhas próprias extensões são `category`, `tags` e `catalog`. Category e tags me ajudam na descoberta e classificação, enquanto com `catalog` consigo rastrear a proveniência. Isso torna particularmente simples implementar um gerenciador de skills que mantém minhas skills atualizadas: ele só precisa acessar o catálogo no endereço fornecido e verificar se a versão da skill foi atualizada ou não. Se você assumir que os agentes só navegarão pelo catálogo eventualmente (em vez de em toda sessão) para escolher a dedo e instalar skills, poderíamos relevar o context bloat de carregar o catálogo inteiro. Mas, na minha visão, o cenário ideal permitiria uma dinâmica um pouco mais inteligente. É aí que entra o `kungfu`. ## "I know kung fu." [`kungfu`](https://github.com/danicat/kungfu) é a ferramenta de CLI que desenvolvi para me ajudar a gerenciar minhas skills. O nome vem da clássica cena de *Matrix* onde Neo tem Kung Fu (a arte marcial) "instalado" em seu cérebro. Esse era o sonho que eu tinha para os meus agentes: instalar conhecimento sob demanda a qualquer momento. Eu tenho uma skill `kungfu` que ensina o agente a usar a CLI do `kungfu`, e essa é a única skill de que preciso. Se o agente precisa de uma nova skill, ele pode usar `kungfu find` para pesquisar no catálogo com base em nome, categoria, tags ou descrição, com tolerância para erros de digitação usando a distância de Levenshtein e sugestões no estilo "você quis dizer?". O comando `find` retornará uma lista dos melhores resultados ranqueados por pontuação, e se o agente precisar de alguma das skills listadas, ele pode carregá-las just-in-time (JIT) via `kungfu load` ou aprendê-las permanentemente via `kungfu learn` (instalando globalmente ou no workspace). A imagem abaixo mostra o fluxo de carregamento JIT: ![Terminal screenshot showing kungfu JIT loading the seo-optimizer skill](image.png "kungfu JIT loading the seo-optimizer skill") O `kungfu` rastreará todas as skills que instalar e você pode verificar as skills disponíveis (instaladas ou online) via `kungfu list`. O comando `kungfu update` se encarrega de atualizar as skills que têm versões mais novas no servidor, fornecendo alguns guardrails para não sobrescrever customizações locais. Como o `kungfu` depende dessa convenção do `catalog.json` e das minhas extensões de metadados no frontmatter, ele requer que os registros exponham um manifesto de catálogo ou que as skills declarem sua origem. Para fazê-lo funcionar sem atritos com repositórios de skills arbitrários por aí, adicionei rastreamento de estado local em `~/.config/kungfu/state.json` para registrar a origem das skills independentemente de como foram instaladas. Você pode estar pensando: por que se dar ao trabalho de adicionar o catálogo aos metadados se você já tem o manifesto? Primeiro, porque gosto de coisas declarativas; segundo, porque isso permite reconciliação e gerenciamento de ciclo de vida mesmo que você não tenha usado o `kungfu` para aprender uma skill. ## Conclusão As Agent Skills são uma das maneiras mais eficazes de conectar modelos fundacionais estáticos aos seus fluxos reais de engenharia no dia a dia. Seja começando pela criação de uma skill simples de documentação para banir padrões desatualizados de SDKs, automatizando tarefas analíticas repetitivas com skills de processo ou explorando swarms de agentes baseados em traços, o investimento se paga rapidamente. Sinta-se à vontade para se inspirar nas ideias em [skills.danicat.dev](https://skills.danicat.dev) ou usar o [`kungfu`](https://github.com/danicat/kungfu) para gerenciá-las e carregá-las sob demanda. Seja qual for o caminho escolhido, o mais importante é continuar refinando seus processos para alcançar todo o potencial do agentic coding. --- # Construindo Agentes de IA em Go: GenAI SDK, Genkit e ADK - **Markdown URL**: https://danicat.dev/pt-br/posts/gemini-for-go-developers-part-3-building-agents/index.md - **HTML URL**: https://danicat.dev/pt-br/posts/gemini-for-go-developers-part-3-building-agents/ - **Date**: 2026-08-25 - **Categories**: Agent Development - **Tags**: adk, ai-agents, cloud-run, gemini, genkit, golang - **Series**: Gemini for Go Developers - **Summary**: Construa um agente Avaliador de Jogos Retrô em Go em três paradigmas: loops de ferramentas com o Go GenAI SDK, fluxos tipados no Genkit e multiagentes no ADK. Usar agentes de programação para escrever código Go é apenas um lado da equação. O outro lado é projetar suas próprias aplicações habilitadas para IA e runtimes de agentes autônomos em Go. Com diversas bibliotecas disponíveis no ecossistema Go, escolher o nível certo de abstração nem sempre é óbvio. Neste guia, vamos dissecar a mecânica fundamental de um agente, definir um domínio concreto (um **Avaliador de Jogos Retrô**) e construí-lo passo a passo em três paradigmas distintos em Go: 1. Um loop de agente de baixo nível construído diretamente com o **[Go GenAI SDK](https://pkg.go.dev/google.golang.org/genai)**. 2. Um pipeline estruturado e orientado a fluxos (*flows*) construído com o **[Genkit](https://genkit.dev)**. 3. Um sistema multiagente modular orientado a sessões construído com o **[Agent Development Kit (ADK)](https://adk.dev)** do Google. Por fim, analisaremos os ambientes de execução (*runtimes*) disponíveis para publicar e hospedar agentes Go em produção na nuvem com alta confiabilidade. O código-fonte completo e executável de todos os exemplos deste artigo está disponível no GitHub no repositório complementar: [**danicat/gemini-for-go-developers**](https://github.com/danicat/gemini-for-go-developers/tree/main/part-3). ## A anatomia de um agente A palavra *agente* é frequentemente usada de forma vaga, mas na engenharia moderna de IA ela possui uma definição arquitetural precisa: um sistema autônomo composto por um **modelo de linguagem (LLM)**, uma ou mais **ferramentas** (funções ou APIs executáveis) e um **harness de execução** operando dentro de um loop de feedback. Sem ferramentas, um modelo é apenas um gerador de texto ou chatbot. Ele só consegue produzir respostas com base nos seus pesos estáticos de treinamento ou no contexto fornecido no prompt. Para conceder ao modelo *agência* — a capacidade de inspecionar estados externos, verificar hipóteses e executar ações no mundo real —, precisamos conectá-lo a ferramentas executáveis. Um detalhe arquitetural crucial é que o modelo de linguagem nunca executa código ou APIs externas diretamente. Em vez disso, o **harness local** (nosso programa em Go) atua como intermediário. Quando o modelo determina que precisa de uma informação externa ou de uma ação executada, ele emite uma requisição estruturada de *chamada de função* (*tool call*). O harness executa a função Go correspondente, captura a saída e injeta o resultado de volta no turno do modelo. O modelo então avalia o novo contexto e decide se precisa chamar outra ferramenta ou se já possui dados suficientes para gerar a resposta final ao usuário. ```mermaid sequenceDiagram autonumber actor User as Usuário participant Harness as Harness do Agente (Go) participant Model as Gemini LLM (com Search Grounding) participant Catalog as Banco de Dados do Catálogo Local User->>Harness: Prompt: "Encontrei EarthBound de SNES por $350. É um bom negócio?" Harness->>Model: Requisição (Prompt de Sistema + Tool search_catalog + Google Search Grounding) Model-->>Harness: Tool Call: search_catalog(query="EarthBound") Harness->>Catalog: Consulta inventário local Catalog-->>Harness: Retorna: owned=true, condition="Cartucho Avulso", price_paid=$180 Harness->>Model: Resposta da Tool: {owned: true, format: "Loose", paid: 180} Note over Model: Gemini executa o Google Search Grounding no servidor para obter preços de mercado Model-->>Harness: Resposta Final em Linguagem Natural (Com citações de busca) Harness->>User: "Você já possui uma cópia avulsa. A $350 por uma cópia Completa na Caixa (CIB), é um excelente negócio..." ``` Nem todas as ferramentas exigem execução local no seu cliente. O Gemini suporta ferramentas nativas integradas, como o **aterramento com a Busca do Google (Google Search grounding)**, executadas diretamente na infraestrutura do Google. Quando declaradas na configuração da sua requisição, a API resolve as buscas de mercado de forma transparente e injeta os metadados de fundamentação na resposta sem exigir idas e vindas na sua rede local. Para regras de negócio e dados privados, contudo, seu harness em Go é o único responsável pela declaração do schema, despacho de argumentos e retorno dos resultados serializados. ## Design do agente: o Avaliador de Jogos Retrô Para comparar nossas três opções de implementação de forma direta, construiremos exatamente o mesmo agente em cada uma das stacks: o **Avaliador de Jogos Retrô** (*Retro Game Appraiser*). Se você coleciona videogames retrô como eu, este é um problema muito familiar. Sempre que visito feiras e mercados de games, mais de uma vez acabei comprando uma cópia de um jogo que adoro só para chegar em casa e perceber que já o tinha na coleção. Para completar, o mercado de colecionismo retrô é famoso pela volatilidade de preços, múltiplas variações de estado de conservação (apenas o cartucho avulso vs. completo na caixa / CIB vs. lacrado de fábrica) e proliferação de cartuchos falsificados (*bootlegs*). Durante uma visita recente a uma feira, percebi que checar os preços no Gemini em tempo real era incrivelmente produtivo: fico feliz em pagar um valor um pouco maior pela experiência presencial e pelo apoio aos vendedores locais, mas ninguém quer ser enganado ou pagar uma fortuna por uma reprodução não autêntica. Nosso agente avaliador resolve os dois lados dessa equação: cruza o inventário pessoal do colecionador e analisa valores de mercado atualizados na web. ### Capacidades e interação do usuário O colecionador conversa com o agente fazendo perguntas naturais como: * *"Eu tenho Chrono Trigger na minha coleção?"* * *"Encontrei uma cópia de EarthBound para Super Nintendo completa na caixa (CIB) em estado impecável por $350. Eu já tenho esse jogo, e esse preço está bom comparado ao mercado atual?"* * *"Quanto eu paguei por Castlevania: Symphony of the Night, e o valor de mercado dele subiu?"* ### Contratos de ferramentas (*tool contracts*) Para responder a essas dúvidas com precisão sem alucinar inventário ou cotações, o agente utiliza duas fontes de verdade: 1. **`search_catalog` (Ferramenta Local):** Função cliente em Go que consulta o banco de dados do colecionador. Ela busca palavras-chave em títulos e plataformas, informando se o item já foi adquirido, seu estado físico, data de aquisição e preço original pago. 2. **`google_search` (Aterramento de Busca):** Ferramenta no servidor que consulta lojas e rastreadores de leilões online para obter valores médios atuais, vendas verificadas recentes e referências de conservação. ### Estratégia de raciocínio Ao ser questionado sobre uma oportunidade de compra, o agente segue um fluxo sistemático: 1. Inspeciona o catálogo local para verificar se o jogo já pertence à coleção e em que formato. 2. Consulta a Busca do Google para determinar a faixa de preço atual para a plataforma e condição física específicas. 3. Sintetiza os dados em uma avaliação prática: compara o valor anunciado com as médias de mercado, destaca oportunidades de upgrade (por exemplo, substituir um cartucho avulso por uma cópia completa com caixa e manual) e oferece uma recomendação clara. Vamos começar implementando esse agente diretamente com o Go GenAI SDK. ## Implementando o agente com o Go GenAI SDK Construir um agente diretamente com o **[Go GenAI SDK](https://pkg.go.dev/google.golang.org/genai)** (`google.golang.org/genai`) representa o **nível mais baixo de abstração**, mapeando 1:1 diretamente sobre o protocolo da API do Gemini. Como não existem camadas de framework entre seu código e a API, você gerencia o loop de despacho de ferramentas, o histórico da conversa e as condições de parada explicitamente. Essa abordagem de baixo nível é excelente para scripts utilitários, pequenos experimentos, aprendizado da mecânica de *function calling* e aterramento, ou quando você precisa de um controle de loop hiperpersonalizado. Por compilar em um binário único e autossuficiente sem dependências pesadas, ele pode ser hospedado em qualquer plataforma padrão de microsserviços (como Google Cloud Run, Kubernetes ou máquinas virtuais). Abaixo está a implementação completa e executável: ```go package main import ( "bufio" "context" "fmt" "log" "os" "os/signal" "strings" "syscall" "google.golang.org/genai" ) // GameItem represents a collectible item in the user's personal inventory. type GameItem struct { Title string `json:"title"` Platform string `json:"platform"` Year int `json:"year"` Condition string `json:"condition"` // e.g. "Loose Cartridge", "CIB (Complete in Box)", "Mint" PricePaid float64 `json:"price_paid"` Notes string `json:"notes"` } // localCatalog simulates an inventory database for retro games. var localCatalog = []GameItem{ { Title: "Chrono Trigger", Platform: "Super Nintendo (SNES)", Year: 1995, Condition: "CIB (Complete in Box)", PricePaid: 210.00, Notes: "Includes original map and registration card.", }, { Title: "EarthBound", Platform: "Super Nintendo (SNES)", Year: 1994, Condition: "Loose Cartridge", PricePaid: 180.00, Notes: "Authentic board verified; label in excellent shape.", }, { Title: "Castlevania: Symphony of the Night", Platform: "Sony PlayStation", Year: 1997, Condition: "CIB (Black Label)", PricePaid: 135.00, Notes: "Original soundtrack disc included.", }, } // searchCatalogTool searches the local collection for matching games. func searchCatalogTool(args map[string]any) map[string]any { query, _ := args["query"].(string) queryLower := strings.ToLower(strings.TrimSpace(query)) var matches []GameItem for _, item := range localCatalog { if strings.Contains(strings.ToLower(item.Title), queryLower) || strings.Contains(strings.ToLower(item.Platform), queryLower) { matches = append(matches, item) } } if len(matches) == 0 { return map[string]any{ "found": false, "message": fmt.Sprintf("No items matching %q found in your collection.", query), } } return map[string]any{ "found": true, "count": len(matches), "results": matches, } } func main() { ctx := context.Background() // Initialise GenAI client for Gemini Enterprise client, err := genai.NewClient(ctx, &genai.ClientConfig{ Project: os.Getenv("GOOGLE_CLOUD_PROJECT"), Location: "global", Backend: genai.BackendEnterprise, }) if err != nil { log.Fatalf("failed to create client: %v", err) } // 1. Declare custom function schema for collection lookup catalogToolDecl := &genai.FunctionDeclaration{ Name: "search_catalog", Description: "Search the collector's personal inventory for owned games by title or platform.", Parameters: &genai.Schema{ Type: genai.TypeObject, Properties: map[string]*genai.Schema{ "query": { Type: genai.TypeString, Description: "Game title or platform to search (e.g. 'EarthBound', 'SNES').", }, }, Required: []string{"query"}, }, } // 2. Configure model tools: custom function declaration + Google Search grounding config := &genai.GenerateContentConfig{ SystemInstruction: &genai.Content{ Parts: []*genai.Part{ {Text: "You are an expert Retro Game Appraiser. When evaluating purchases, check the user's " + "collection catalog first to see if they already own the item, then check current market " + "prices using Google Search to evaluate whether the deal is fair, overpriced, or a bargain."}, }, }, Tools: []*genai.Tool{ { FunctionDeclarations: []*genai.FunctionDeclaration{catalogToolDecl}, }, { GoogleSearch: &genai.GoogleSearch{}, }, }, } // 3. Graceful shutdown on Ctrl+C (SIGINT) or SIGTERM sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) go func() { <-sigChan fmt.Println("\nGoodbye!") os.Exit(0) }() model := "gemini-3.8-flash" var contents []*genai.Content fmt.Println("Retro Game Appraiser (SDK Agent)") fmt.Println("Type your question below, or 'exit' (Ctrl+C / Ctrl+D) to quit.") fmt.Println("-----------------------------------------------------------------") scanner := bufio.NewScanner(os.Stdin) for { fmt.Print("\nUser: ") if !scanner.Scan() { fmt.Println("\nGoodbye!") break } input := strings.TrimSpace(scanner.Text()) if input == "" { continue } if strings.EqualFold(input, "exit") { fmt.Println("Goodbye!") break } contents = append(contents, &genai.Content{ Role: "user", Parts: []*genai.Part{genai.NewPartFromText(input)}, }) // 4. The Agent Loop: model generation -> tool dispatch -> feedback -> until final answer for { resp, err := client.Models.GenerateContent(ctx, model, contents, config) if err != nil { log.Printf("error generating content: %v", err) break } if len(resp.Candidates) == 0 || resp.Candidates[0].Content == nil { log.Println("received empty response candidate from model") break } // Append the model's response to the conversation history modelContent := resp.Candidates[0].Content contents = append(contents, modelContent) // Check if the model requested any client-side tool executions funcCalls := resp.FunctionCalls() if len(funcCalls) == 0 { fmt.Printf("\nAppraiser: %s\n", resp.Text()) break } // Execute each requested tool and prepare response parts var responseParts []*genai.Part for _, call := range funcCalls { fmt.Printf("[Harness] Executing tool: %s(args=%v)\n", call.Name, call.Args) var result map[string]any switch call.Name { case "search_catalog": result = searchCatalogTool(call.Args) default: result = map[string]any{"error": fmt.Sprintf("unsupported tool: %s", call.Name)} } responseParts = append(responseParts, genai.NewPartFromFunctionResponse(call.Name, result)) } // Return tool execution results as a user turn contents = append(contents, &genai.Content{ Role: "user", Parts: responseParts, }) } } } ``` ### Executando o agente com o SDK Para executar este exemplo, certifique-se de ter configurado seu projeto no Google Cloud e efetuado o login com Application Default Credentials (`gcloud auth application-default login`): ```sh export GOOGLE_CLOUD_PROJECT="your-gcp-project-id" go run main.go ``` A sessão interativa de chat é iniciada diretamente no seu terminal. Você pode conversar com o avaliador ao longo de múltiplos turnos e encerrar a qualquer momento digitando `exit`, pressionando `Ctrl+C` ou enviando um `EOF` via `Ctrl+D`: ```text Retro Game Appraiser (SDK Agent) Type your question below, or 'exit' (Ctrl+C / Ctrl+D) to quit. ----------------------------------------------------------------- User: I found a copy of EarthBound for SNES in mint Complete-in-Box (CIB) condition for $350. Do I already own it, and is $350 a good deal compared to current market prices? [Harness] Executing tool: search_catalog(args=map[query:EarthBound]) Appraiser: Here is your collection check and appraisal for **EarthBound (SNES)**: 1. **Current Collection Status**: - You currently own **EarthBound** on Super Nintendo as a **Loose Cartridge**, purchased for **$180.00**. 2. **Market Price Appraisal**: - Verified market sales for an authentic, **Complete-in-Box (CIB)** copy of EarthBound typically range between **$1,200.00 and $1,500.00** depending on the condition of the box, tray, and original player's guide. 3. **Recommendation**: - At **$350.00**, a genuine Mint CIB copy is an **exceptional deal** (more than 70% below prevailing market value). - **Caution**: Because EarthBound is one of the most heavily counterfeited SNES titles, inspect the box printing, registration card, and PCB board carefully before completing the transaction. If verified authentic, this is an outstanding opportunity to upgrade your loose copy to CIB. User: exit Goodbye! ``` A implementação com o SDK torna o fluxo mecânico totalmente explícito. Hoje, com modelos de última geração e janelas generosas de tokens, é tentadoramente fácil escrever o encanamento, os loops de despacho e todo o andaime de suporte por conta própria. No entanto, só porque você *consegue* construir todo o encanamento do zero, não significa que *deva*. Cada linha de código de framework customizado que você escreve se transforma em um custo contínuo de manutenção e em uma fonte potencial de bugs sutis de concorrência e serialização. Escrever seu próprio framework de agentes é, na melhor das hipóteses, uma distração que atrasa a entrega de valor e, na pior, uma montanha de débito técnico que desvia seu esforço do objetivo de negócio que você quer alcançar. O melhor código é aquele que não precisa ser escrito; o segundo melhor é aquele que resolve seu problema com a menor quantidade de código sob sua responsabilidade. ## Frameworks para desenvolvimento de agentes Enquanto o loop com o SDK puro é imbatível para entender a mecânica básica ou criar loops altamente customizados, aplicações em produção exigem níveis superiores de abstração: * **Inferência Automática de Schemas:** Definir schemas JSON manualmente com `&genai.Schema{...}` é repetitivo e suscetível a erros. Frameworks modernos inferem schemas diretamente de structs Go e comentários de documentação. * **Observabilidade e Rastreamento Distribuído:** Em produção, você precisa de traces do OpenTelemetry, métricas de latência por ferramenta e contagem de tokens sem precisar instrumentar cada função manualmente. * **Gerenciamento de Prompts:** Manter prompts fixos no código dificulta o trabalho colaborativo com engenheiros de prompt e impede o versionamento de templates separado do binário. * **Persistência de Sessão e Estado:** Gerenciar históricos de conversas multi-turno entre requisições HTTP sem estado exige camadas de armazenamento desacopladas e seguras para concorrência. * **Portabilidade de Modelos:** Enquanto o SDK Go é exclusivo para a família Gemini, frameworks permitem trocar provedores ou testar modelos locais sem reescrever a lógica de negócio. Para atender a essas demandas em diferentes cenários arquiteturais, temos dois frameworks de código aberto no ecossistema Go: **Genkit** e **Agent Development Kit (ADK)**. | Dimensão | Go GenAI SDK | Genkit Go | Agent Development Kit (ADK) | | :--- | :--- | :--- | :--- | | **Nível de Abstração** | **Baixo** (Mapeamento 1:1 com a API Gemini) | **Médio** (Workflows estruturados) | **Alto** (Sistemas multiagente autônomos) | | **Arquitetura Central** | Loop `for` e despacho explícitos | **Flows** (`genkit.DefineFlow`) e Tools | **Agents**, Runners e Serviços de Sessão | | **Ponto Ideal de Uso** | Scripts, aprendizado da mecânica, loops sob medida | Aplicações pontuais (CLIs, web services), pipelines determinísticos, agentes de domínio único | Agentes conversacionais, orquestração multiagente, memória de longo prazo e RAG corporativo | | **Ecossistema de Modelos** | Específico para Gemini | Multimodelo (Google GenAI, Vertex AI, Ollama, etc.) | Multimodelo (via adaptadores do ADK) | | **Destino Recomendado** | Qualquer host HTTP (Cloud Run, K8s, VMs) | Qualquer backend; **Cloud Run** (preferencial) | **Gemini Enterprise** (preferencial com sessões/RAG/memória) ou **Cloud Run** | Vejamos como nosso Avaliador de Jogos Retrô é implementado em cada um desses frameworks. ## Implementando o agente com o Genkit O **[Genkit](https://genkit.dev)** opera em um **nível médio de abstração**, trazendo disciplina de engenharia de software e observabilidade integrada para aplicações com IA. No Genkit, tudo é estruturado em torno de **Flows** (pipelines fortemente tipados e observáveis) e **Tools** (funções Go type-safe com geração automática de schemas). O Genkit é a escolha ideal para aplicações com **gerações pontuais** (como ferramentas de linha de comando, processamento em lote e webhooks), fluxos determinísticos e agentes focados em um único domínio (como o [pipeline multimodal de restauração de fotos](https://danicat.dev/pt-br/posts/20260216-genkit-go/) que construí anteriormente). Ele suporta múltiplos provedores através de plugins e, por ser executado como um servidor HTTP padrão em Go, pode rodar em qualquer infraestrutura — sendo o **Google Cloud Run** o destino preferencial pela simplicidade no gerenciamento de contêineres e escalabilidade automática. Abaixo está o Avaliador de Jogos Retrô construído com o Genkit Go: ```go package main import ( "context" "fmt" "log" "net/http" "os" "os/signal" "strings" "syscall" "time" "github.com/firebase/genkit/go/ai" "github.com/firebase/genkit/go/genkit" "github.com/firebase/genkit/go/plugins/googlegenai" "google.golang.org/genai" ) // GameItem represents a collectible item in the user's personal inventory. type GameItem struct { Title string `json:"title"` Platform string `json:"platform"` Year int `json:"year"` Condition string `json:"condition"` PricePaid float64 `json:"price_paid"` Notes string `json:"notes"` } // localCatalog simulates an inventory database for retro games. var localCatalog = []GameItem{ { Title: "Chrono Trigger", Platform: "Super Nintendo (SNES)", Year: 1995, Condition: "CIB (Complete in Box)", PricePaid: 210.00, Notes: "Includes original map and registration card.", }, { Title: "EarthBound", Platform: "Super Nintendo (SNES)", Year: 1994, Condition: "Loose Cartridge", PricePaid: 180.00, Notes: "Authentic board verified; label in excellent shape.", }, { Title: "Castlevania: Symphony of the Night", Platform: "Sony PlayStation", Year: 1997, Condition: "CIB (Black Label)", PricePaid: 135.00, Notes: "Original soundtrack disc included.", }, } type CatalogRequest struct { Query string `json:"query" jsonschema:"description=The game title or platform to search in the inventory"` } type CatalogResponse struct { Found bool `json:"found"` Message string `json:"message,omitempty"` Count int `json:"count,omitempty"` Results []GameItem `json:"results,omitempty"` } type AppraiserRequest struct { Prompt string `json:"prompt" jsonschema:"description=The collector's question or purchase offer to evaluate"` } type AppraiserResponse struct { Appraisal string `json:"appraisal"` } func main() { ctx := context.Background() // 1. Initialise Genkit with Vertex AI plugin g := genkit.Init(ctx, genkit.WithPlugins(&googlegenai.VertexAI{ ProjectID: os.Getenv("GOOGLE_CLOUD_PROJECT"), Location: "global", }), ) // 2. Define strongly-typed tool with automatic schema generation catalogTool := genkit.DefineTool( g, "search_catalog", "Search the collector's personal inventory for owned games by title or platform.", func(ctx *ai.ToolContext, req CatalogRequest) (CatalogResponse, error) { queryLower := strings.ToLower(strings.TrimSpace(req.Query)) queryWords := strings.Fields(queryLower) var matches []GameItem for _, item := range localCatalog { itemText := strings.ToLower(item.Title + " " + item.Platform) allMatch := true for _, word := range queryWords { if !strings.Contains(itemText, word) { allMatch = false break } } if allMatch { matches = append(matches, item) } } if len(matches) == 0 { return CatalogResponse{ Found: false, Message: fmt.Sprintf("No items matching %q found in personal collection.", req.Query), }, nil } return CatalogResponse{ Found: true, Count: len(matches), Results: matches, }, nil }, ) // 3. Define structured appraisal flow with typed request and response appraiserFlow := genkit.DefineFlow( g, "appraise_game", func(ctx context.Context, req AppraiserRequest) (AppraiserResponse, error) { resp, err := genkit.Generate(ctx, g, ai.WithModelName("vertexai/gemini-3.8-flash"), ai.WithSystem( "You are an expert Retro Game Appraiser. Assist collectors by evaluating prospective purchases, "+ "cross-referencing their personal inventory, and assessing fair market valuations. "+ "Always search the collection catalog using search_catalog before providing purchase recommendations.", ), ai.WithConfig(&genai.GenerateContentConfig{ ThinkingConfig: &genai.ThinkingConfig{IncludeThoughts: true}, Tools: []*genai.Tool{ { GoogleSearch: &genai.GoogleSearch{}, }, }, }), ai.WithPrompt(req.Prompt), ai.WithTools(catalogTool), ) if err != nil { return AppraiserResponse{}, fmt.Errorf("appraisal generation failed: %w", err) } return AppraiserResponse{Appraisal: resp.Text()}, nil }, ) // 4. Mount flow directly using Genkit's built-in HTTP handler mux := http.NewServeMux() mux.Handle("POST /api/appraise", genkit.Handler(appraiserFlow)) port := os.Getenv("PORT") if port == "" { port = "8080" } server := &http.Server{ Addr: ":" + port, Handler: mux, } // Graceful shutdown on Ctrl+C (SIGINT) or SIGTERM serverCtx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) defer stop() go func() { log.Printf("Retro Game Appraiser (Genkit) listening on :%s", port) if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("server failed: %v", err) } }() <-serverCtx.Done() log.Println("\nShutting down server gracefully...") shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := server.Shutdown(shutdownCtx); err != nil { log.Fatalf("server forced shutdown: %v", err) } log.Println("Server exited cleanly.") } ``` ### Executando o fluxo com o Genkit Inicie o servidor Genkit exportando a variável do projeto Google Cloud: ```sh export GOOGLE_CLOUD_PROJECT="your-gcp-project-id" export PORT=8080 go run main.go ``` Em outro terminal, envie uma solicitação de avaliação: ```sh curl -s -X POST http://localhost:8080/api/appraise \ -H "Content-Type: application/json" \ -d '{"data": {"prompt": "I found a copy of EarthBound for SNES for $350. Do I own it, and is it a good deal?"}}' | jq . ``` O fluxo executa, consulta a ferramenta de catálogo e retorna o resultado estruturado: ```json { "appraisal": "### 1. Catalog Check\n**Yes, you already own it.**\n* **Title:** *EarthBound* (SNES, 1994)\n* **Status in Collection:** Loose Cartridge\n* **Condition/Notes:** Authentic board verified; label in excellent shape.\n* **Price Paid:** $180\n\n---\n\n### 2. Market Appraisal & Deal Analysis\n* **Loose Cartridge:** The current going market rate for an authentic loose copy ranges between **$320 and $380**. At **$350**, it is priced right at **fair market value**—neither an overpriced listing nor a significant bargain.\n* **Complete in Box (CIB) / Boxed with Guide:** If this listing happens to include the original big box and strategy guide with scratch-and-sniff cards, $350 would be an extraordinary steal (CIB copies regularly sell for **$1,500–$2,500+**).\n\n---\n\n### 3. Recommendation\n* **Pass (if Loose):** Since you already have an authentic copy in excellent condition, paying retail market price ($350) for a duplicate loose cart does not offer strong value or upside.\n* **Buy immediately (if Complete/Boxed):** Only pull the trigger if it includes the original packaging or represents a major condition upgrade/variant.\n* **Buyer Beware:** If you do ever consider another copy, always inspect the PCB (printed circuit board) screws and chips, as *EarthBound* is one of the most frequently counterfeited games on the SNES." } ``` O Genkit elimina a necessidade do loop manual. Ele cuida da desserialização dos argumentos em structs nativas de Go, executa a função, encaminha a resposta de volta ao modelo e gera spans de telemetria automaticamente para cada etapa. É a escolha ideal para fluxos determinísticos e estruturados. ## Implementando o agente com o Agent Development Kit (ADK) Enquanto o Genkit se concentra em pipelines de aplicação estruturados, o **[Agent Development Kit (ADK)](https://adk.dev)** do Google opera em um **alto nível de abstração**, projetado especificamente para agentes conversacionais autônomos, orquestração multiagente e arquiteturas complexas que demandam memória persistente e RAG corporativo. O ADK padroniza ciclos de vida de agentes, delegação para subagentes e protocolos de comunicação agente a agente (A2A). Assim como o Genkit, ele suporta múltiplos provedores através de adaptadores modulares de modelo. Quanto ao ambiente de hospedagem, a **Gemini Enterprise Agent Platform** é o destino preferencial caso você deseje usufruir de persistência gerenciada de sessões, conectores corporativos de aterramento e serviços de memória sem precisar programar bancos de dados próprios. Caso prefira gerenciar seu próprio estado em microsserviços conteinerizados, o **Google Cloud Run** é a escolha ideal. Abaixo está o Avaliador de Jogos Retrô implementado com o ADK v2: ```go package main import ( "context" "fmt" "log" "os" "strings" "google.golang.org/genai" "google.golang.org/adk/v2/agent" "google.golang.org/adk/v2/agent/llmagent" "google.golang.org/adk/v2/cmd/launcher" "google.golang.org/adk/v2/cmd/launcher/full" "google.golang.org/adk/v2/model/gemini" "google.golang.org/adk/v2/tool" "google.golang.org/adk/v2/tool/functiontool" "google.golang.org/adk/v2/tool/geminitool" ) // GameItem represents a collectible item in the user's personal inventory. type GameItem struct { Title string `json:"title"` Platform string `json:"platform"` Year int `json:"year"` Condition string `json:"condition"` PricePaid float64 `json:"price_paid"` Notes string `json:"notes"` } // localCatalog simulates an inventory database for retro games. var localCatalog = []GameItem{ { Title: "Chrono Trigger", Platform: "Super Nintendo (SNES)", Year: 1995, Condition: "CIB (Complete in Box)", PricePaid: 210.00, Notes: "Includes original map and registration card.", }, { Title: "EarthBound", Platform: "Super Nintendo (SNES)", Year: 1994, Condition: "Loose Cartridge", PricePaid: 180.00, Notes: "Authentic board verified; label in excellent shape.", }, { Title: "Castlevania: Symphony of the Night", Platform: "Sony PlayStation", Year: 1997, Condition: "CIB (Black Label)", PricePaid: 135.00, Notes: "Original soundtrack disc included.", }, } type CatalogRequest struct { Query string `json:"query" jsonschema:"The game title or platform to search in the inventory."` } type CatalogResponse struct { Found bool `json:"found"` Message string `json:"message,omitempty"` Count int `json:"count,omitempty"` Results []GameItem `json:"results,omitempty"` } func main() { ctx := context.Background() // 1. Initialise Gemini Model adapter for Gemini Enterprise model, err := gemini.NewModel(ctx, "gemini-3.8-flash", &genai.ClientConfig{ Project: os.Getenv("GOOGLE_CLOUD_PROJECT"), Location: "global", Backend: genai.BackendEnterprise, }) if err != nil { log.Fatalf("failed to create Gemini model: %v", err) } // 2. Wrap collection lookup as an ADK Function Tool catalogTool, err := functiontool.New(functiontool.Config{ Name: "search_catalog", Description: "Search the collector's personal inventory for owned games by title or platform.", }, func(ctx agent.Context, req CatalogRequest) (CatalogResponse, error) { queryLower := strings.ToLower(strings.TrimSpace(req.Query)) var matches []GameItem for _, item := range localCatalog { if strings.Contains(strings.ToLower(item.Title), queryLower) || strings.Contains(strings.ToLower(item.Platform), queryLower) { matches = append(matches, item) } } if len(matches) == 0 { return CatalogResponse{ Found: false, Message: fmt.Sprintf("No items matching %q found in personal collection.", req.Query), }, nil } return CatalogResponse{ Found: true, Count: len(matches), Results: matches, }, nil }) if err != nil { log.Fatalf("failed to create catalog tool: %v", err) } // 3. Define autonomous LLM Agent appraiserAgent, err := llmagent.New(llmagent.Config{ Name: "retro_game_appraiser", Model: model, Description: "Expert appraiser that analyzes retro video game purchases and collection inventory.", Instruction: "You are an expert Retro Game Appraiser. Assist collectors by verifying collection " + "status with search_catalog, assessing condition variants, and offering objective buying recommendations.", Tools: []tool.Tool{ catalogTool, geminitool.GoogleSearch{}, }, }) if err != nil { log.Fatalf("failed to create appraiser agent: %v", err) } // 4. Configure launcher and execute config := &launcher.Config{ AgentLoader: agent.NewSingleLoader(appraiserAgent), } l := full.NewLauncher() if err = l.Execute(ctx, config, os.Args[1:]); err != nil { log.Fatalf("run failed: %v\n\n%s", err, l.CommandLineSyntax()) } } ``` ### Executando o agente com o ADK O ADK disponibiliza um launcher universal (`full.NewLauncher()`) que elimina a necessidade de construir roteamento HTTP manual, serialização JSON ou boilerplate de gerenciamento de sessões. Passando argumentos pela linha de comando para o launcher, você pode executar o agente em múltiplos modos de interação sem alterar uma única linha de código. Para iniciar uma sessão interativa no terminal, execute: ```sh export GOOGLE_CLOUD_PROJECT="your-gcp-project-id" go run main.go ``` O launcher inicia em modo console, permitindo conversar com o avaliador diretamente pelo terminal: ```text User: Do I have Chrono Trigger in my collection? Agent: Yes, you have Chrono Trigger in your collection! Here are the details from your inventory: * Title: Chrono Trigger * Platform: Super Nintendo (SNES) * Release Year: 1995 * Condition: CIB (Complete in Box) * Price Paid: $210.00 * Notes: Includes original map and registration card. User: What did I pay for it? Agent: You paid $210.00 for it. ``` Como o ADK gerencia o estado da conversa e a continuidade da sessão automaticamente, perguntas subsequentes retêm todo o histórico contextual das rodadas anteriores. Como alternativa, você pode iniciar o servidor com a Web UI de desenvolvedor e a API REST integradas do ADK: ```sh go run main.go web webui api ``` Ao acessar `http://localhost:8080`, uma interface web interativa de chat é disponibilizada, oferecendo respostas em streaming em tempo real, histórico de sessões e visibilidade transparente da execução das ferramentas (tanto da tool customizada `search_catalog` quanto do aterramento nativo com `GoogleSearch`). O ADK entrega uma separação arquitetural limpa entre a definição do agente (`llmagent`), as ferramentas (`functiontool` e ferramentas nativas como `geminitool.GoogleSearch`) e os ambientes de execução (`launcher` e `runner`), tornando-se a escolha ideal para hierarquias multiagente complexas e assistentes conversacionais. ## Runtimes para execução de agentes Programar o código do agente em Go é apenas metade da jornada. Depois de pronto, você precisa de um ambiente de execução capaz de suportar respostas em streaming de longa duração, execuções de ferramentas em segundo plano, credenciais seguras e picos de tráfego sem complicações operacionais. Como Go compila em binários únicos com consumo mínimo de recursos, os agentes em Go são extremamente rápidos e baratos de hospedar. Dependendo do framework e dos requisitos de estado, você conta com dois destinos principais: ### Cloud Run: o ponto ideal para backends conteinerizados O **[Google Cloud Run](https://cloud.google.com/run)** é o principal destino para hospedar agentes Go em contêineres — sejam eles construídos diretamente com o Go GenAI SDK, fluxos do Genkit ou runners do ADK. Utilizando builds em múltiplos estágios no Docker (*multi-stage builds*), você pode compilar seu agente Go em imagens mínimas `scratch` ou `distroless` sem dependências de tempo de execução. Sem a necessidade de inicializar interpretadores pesados ou máquinas virtuais, seu serviço escala de zero rapidamente, minimizando a sobrecarga de *cold start* que frequentemente afeta ambientes mais pesados. Vantagens operacionais do Cloud Run para agentes Go: * **Escala a Zero Real e Alta Concorrência:** Pague apenas pelos milissegundos de CPU consumidos durante a execução ativa do agente ou o processamento de respostas de ferramentas. As goroutines leves do Go permitem que uma única instância atenda simultaneamente centenas de turnos com consumo irrisório de memória. * **Timeouts Estendidos (Até 60 Minutos):** Embora o timeout padrão seja de 5 minutos, o Cloud Run suporta limites de até 60 minutos (3.600 segundos) — garantindo que raciocínios em múltiplas etapas, tarefas profundas de pesquisa e enxames de subagentes terminem sem interrupções precoces. * **Streaming Bidirecional e WebSockets:** Suporte nativo a HTTP/2 chunked transfer encoding, Server-Sent Events (SSE) e WebSockets. Para agentes multimodais de voz ou tempo real com a Gemini Live API, WebSockets viabilizam fluxos contínuos entre cliente e servidor. * **Afinidade de Sessão (Sticky Sessions):** Se o seu agente mantém caches temporários em memória ou contexto local entre turnos consecutivos, é possível habilitar a afinidade de sessão (`--session-affinity`) via IP do cliente ou cookies para direcionar requisições à mesma instância. * **Resiliência Desacoplada:** Para produção, o estado deve ser persistido em bancos gerenciados (como Firestore, Redis ou Cloud SQL). Se ocorrer uma queda de conexão em um turno longo, o agente pode retomar a execução pelo ID da interação sem perda de contexto. * **Autenticação Segura com Workload Identity IAM:** Zero chaves de API fixas no código. O agente Go se autentica na Gemini Developer API, Vertex AI e Cloud Storage usando a conta de serviço ambiente do Cloud Run. ### Gemini Enterprise Agent Platform: sessões gerenciadas e RAG corporativo Ao construir com o **Agent Development Kit (ADK)**, especialmente para soluções corporativas que exigem sessões duradouras, memória de longo prazo e aterramento em dados da empresa, a **Gemini Enterprise Agent Platform** (a evolução do Vertex AI Agent Engine) oferece um ecossistema serverless totalmente gerenciado. Em vez de provisionar bancos de dados e escrever adaptadores manuais de persistência, a plataforma fornece: * **Persistência Desacoplada de Sessão:** Separação limpa entre o armazenamento duradouro de histórico (`SessionService` no ADK) e os loops efêmeros de streaming (`LiveSession` com recuperação automática em caso de reconexão). * **Aterramento Corporativo e Busca Vetorial:** Conectores nativos para fontes de conhecimento corporativo (Google Drive, BigQuery, repositórios internos) e índices do Vertex AI Vector Search para recuperação semântica de alto desempenho. * **Execução Segura em Sandbox:** Ambientes isolados onde os agentes podem gerar e executar código dinamicamente (análise de dados, scripts Python/Go) sem expor a infraestrutura hospedeira a riscos. * **Protocolos Agente a Agente (A2A):** Protocolos padronizados que permitem a agentes independentes descobrir capacidades, negociar contratos e delegar tarefas entre si através de diferentes áreas da organização. * **Segurança, Identidade do Agente e Model Armor:** Permissões granulares de IAM, perímetros de VPC Service Controls e proteção em tempo de execução com o Model Armor contra injeção de prompt, vazamento de dados e violações de política. ## Para onde ir a partir daqui Construir o mesmo agente de três formas deixa os trade-offs claros: o **Go GenAI SDK** dá controle total sobre o loop de execução, o **Genkit** brilha em fluxos fortemente tipados, gestão de prompts e serviços HTTP, e o **ADK** oferece primitivas estruturadas para sistemas multiagentes orientados a sessões. Se você quiser ver como uma aplicação real evolui além deste exemplo inicial, confira [**Por Que Reescrevi Meu Agente RAG em Python Usando Go e Genkit**](https://danicat.dev/pt-br/posts/20261006-building-agentic-backends-with-genkit-go/), onde refatoramos um agente legado em Python com RAG em um binário Go compilado usando templates `dotprompt` tipados, middleware de Agent Skills e delegação multiagente. Boas programações! --- # Por Que Go Foi Feito para Agentes de Código (E Como Ajustar o Seu) - **Markdown URL**: https://danicat.dev/pt-br/posts/gemini-for-go-developers-part-2-coding-with-gemini/index.md - **HTML URL**: https://danicat.dev/pt-br/posts/gemini-for-go-developers-part-2-coding-with-gemini/ - **Date**: 2026-08-17 - **Categories**: Agentic Coding - **Tags**: agent-skills, antigravity, gemini, godoctor, golang, mcp - **Series**: Gemini for Go Developers - **Summary**: Maximize a afinidade agentiva do Go com harnesses de programação por IA. Configure o Antigravity, servidores MCP, Agent Skills e o godoctor para fluxos em Go. Como obter os melhores resultados ao combinar agentes de programação por IA com Go? Neste artigo, vamos explorar como configurar um ambiente eficiente e de alto sinal para programação agentiva em Go. Começaremos analisando por que a escolha da linguagem de programação ainda importa na era da IA, exploraremos o ecossistema de harnesses de agentes e padrões abertos de customização (regras, MCP, skills, hooks, subagentes e plugins) e finalizaremos com uma configuração prática para maximizar a afinidade agentiva do Go no seu fluxo de trabalho diário. ## Por que usar Go na era da IA? Antes de irmos mais a fundo, vamos tirar da frente a pergunta óbvia: na era da IA, a nossa escolha de linguagem de programação ainda importa? No passado, a seleção de uma linguagem era quase sempre ditada pela experiência prévia da equipe. Aprender uma nova sintaxe, novos idiomas e as peculiaridades do ferramental tomava um tempo valioso; por isso, os times permaneciam na sua zona de conforto, a menos que fossem forçados a mudar por uma grande transição tecnológica. A IA virou essa dinâmica de ponta-cabeça. A sintaxe não é mais uma barreira quando modelos geram boilerplate sob demanda, e aprender uma stack desconhecida com um tutor de IA supera de longe passar horas rezando para que alguma thread de cinco anos atrás no Stack Overflow corresponda exatamente ao seu erro de compilação. Por que você se importaria com a escolha da linguagem, então? Tudo se resume a dois temas principais: o **ecossistema da linguagem** e a **afinidade agentiva**. Nesse contexto, o ecossistema da linguagem refere-se a tudo o que a linguagem atrai por "gravidade": SDKs ativamente mantidos, bibliotecas, documentação, a vitalidade da comunidade e o conhecimento acumulado do setor. Mas a **afinidade agentiva** é exclusiva do mundo em que vivemos hoje. Eu a defino como "o quão fácil é guiar um agente para programar nessa linguagem". Uma alta afinidade agentiva é influenciada, em primeiro lugar, por quão preparados os modelos estão para gerar código nessa linguagem e, em segundo lugar, por quão preparados estão para adotar o ferramental necessário para verificar, testar e manter esse código. A afinidade agentiva depende naturalmente de quanto dado sobre uma determinada linguagem estava disponível durante o treinamento do modelo, beneficiando linguagens populares com código público e literatura extensos. Dito isso, volume sozinho não é tudo: linguagens mais antigas que passaram por mudanças massivas de paradigma sofrem com práticas fragmentadas, fazendo com que os modelos sugiram padrões obsoletos no momento da inferência. Na minha experiência pessoal, linguagens como Go, Python e JavaScript têm alta afinidade agentiva por padrão. O Go se destaca em particular: sua legibilidade, tipagem estática rigorosa e a filosofia quase pythônica de que "o explícito é melhor do que o implícito" tornam a geração de código muito menos propensa a alucinações de sintaxe. Mais importante ainda: a compilação rápida do Go, seus testes nativos integrados e o ferramental padrão opinativo oferecem aos agentes de código um loop de feedback imediato e determinístico para capturar e corrigir erros de forma autônoma. (Para um mergulho mais profundo em como a filosofia de design do Go se alinha à engenharia de software assistida por IA, confira [este ensaio de Cameron Balahan e Richard Seroter no Google Developers Blog](https://developers.googleblog.com/why-go-is-an-ideal-language-for-ai-assisted-software-engineering/)). Por outro lado, linguagens como R e C ficam no outro extremo do espectro. No caso do R, é uma linguagem acadêmica de nicho em que, mesmo com suporte total de agentes, não consigo manter meu pacote [read.dbc](https://github.com/danicat/read.dbc) no CRAN (o sistema de distribuição de pacotes do R) porque nem eu nem os modelos conseguimos reproduzir o problema reportado pelo pipeline do CRAN. Em C, a falta de salvaguardas significa que erros simples podem se transformar em bugs catastróficos e silenciosos que os modelos têm dificuldade para capturar cedo sem uma rica ergonomia de ferramentas. Em última análise, a afinidade agentiva é medida pela velocidade e capacidade de resposta do "loop" agentivo: se o seu agente consegue compilar, testar, rodar benchmarks e corrigir o código sem necessidade de microgerenciamento humano constante. ## Selecionando a superfície agentiva correta Embora a corrida dos modelos de fronteira seja empolgante de acompanhar, os modelos em si são apenas parte da equação quando se trata de geração de código. A qualidade das respostas e a experiência do desenvolvedor ao programar com um agente são fortemente influenciadas pelo harness que está executando o modelo. Se você está programando com o Gemini, a escolha natural é o **ecossistema Antigravity**. Com o lançamento do Antigravity 2.0, o Google separou o ecossistema em três superfícies distintas, dependendo de como você prefere trabalhar: ### Antigravity 2.0 No centro do ecossistema Antigravity está o aplicativo desktop **Antigravity 2.0**, às vezes também chamado de **Agent Manager**. A maior mudança aqui é colocar a experiência do agente no centro do palco, enquanto o código fica nos bastidores. Para usuários de primeira viagem acostumados a uma IDE, a experiência pode ser um pouco chocante: não há árvores de arquivos para explorar a base de código, nem qualquer forma de editar arquivos manualmente. Cada interação é feita através do agente. O seu controle reside em dizer ao agente o que fazer e anotar o trabalho dele com comentários no estilo "Google Docs". ### O Antigravity CLI (`agy`) Embora o conceito de Agent Manager seja bastante novo, interfaces de terminal para agentes já existem há mais tempo, popularizadas por Claude, Aider, Cline e outros. Em junho de 2025, o Google também lançou sua própria interface de terminal, o Gemini CLI, mas desde então ele foi descontinuado em favor do novo **Antigravity CLI** (`agy`). Um detalhe que me deixa particularmente feliz como Gopher é que o `agy` foi escrito em Go (enquanto o Gemini CLI era em TypeScript), resultando em uma experiência de terminal visivelmente mais rápida e ágil. ### O Antigravity IDE Se você ainda prefere um editor de código visual e dedicado, o Google oferece o **Antigravity IDE** como uma aplicação companheira separada. Ele é baseado no VS Code, então todos os elementos familiares da IDE continuam onde você se lembra, além de contar com um painel lateral de agente para interações com o Gemini. Para ser 100% transparente, hoje em dia eu raramente abro a IDE para programar. As únicas vezes em que realmente uso a IDE são quando estou escrevendo ou revisando artigos (como este), já que ainda faço uma parte enorme do meu processo de escrita manualmente. Para código, muito raramente edito qualquer coisa à mão atualmente. ## Padrões agentivos aplicados à programação Independentemente da superfície escolhida, o Antigravity é muito capaz assim que sai da caixa, mas tem suas peculiaridades. A melhor forma de aproveitar ao máximo as capacidades do Antigravity é equipar o seu agente com **customizações**. O Antigravity suporta padrões agentivos tanto consolidados quanto emergentes: regras (*rules*), skills, MCP, hooks, subagentes, sidecars e plugins. No entanto, o suporte a essas customizações, infelizmente, **não é uniforme** entre as diferentes superfícies do Antigravity hoje. Vamos ver como cada uma dessas customizações funciona na prática. ### Instruções e regras para agentes O conceito de instruções foi padronizado pela iniciativa [**AGENTS.md**](https://agents.md/), que o Antigravity suporta por meio de arquivos `AGENTS.md` ou `GEMINI.md` (assim como regras modulares sob `.gemini/rules/` ou `.agents/rules/`). Você pode pensar no `AGENTS.md` como o `README.md` para agentes de IA. É o lugar para guardar contexto do projeto, restrições arquiteturais, comandos de teste e preferências de estilo que são essenciais para o agente saber, mas que poluiriam a documentação voltada para humanos. Para ser completamente sincera, com o advento das Agent Skills, raramente atualizo o `GEMINI.md` hoje em dia, e a maioria dos meus repositórios provavelmente tem arquivos desatualizados (minha culpa, coitados dos agentes!). Dito isso, eles ainda são úteis para direcionar os agentes para o caminho certo. Só tenha em mente que instruções baseadas em prompts atuam mais como recomendações do que como guardrails: um agente ainda pode ignorar seletivamente ou se afastar das regras durante uma sessão longa. ### Model Context Protocol (MCP) O [**Model Context Protocol**](https://modelcontextprotocol.io/) (MCP) é um padrão aberto para conectar aplicações de IA a ferramentas e fontes de dados externas. O protocolo expõe três primitivas centrais: 1. **Tools:** Funções executáveis que o agente pode invocar (por exemplo, consultar um banco de dados, rodar um linter, verificar um build). 2. **Resources:** Fontes de dados somente leitura que o agente pode inspecionar (por exemplo, arquivos de documentação, esquemas de banco de dados, logs do sistema). 3. **Prompts:** Modelos pré-definidos de fluxo de trabalho. No mundo real, nós nos importamos mais com as tools do que com qualquer outra coisa, e a maioria dos clientes nem sequer oferece suporte a resources ou prompts. Resources podem ser emulados por meio de tools (uma tool dedicada pode recuperar dados) e prompts caíram em grande parte no esquecimento devido à introdução das Agent Skills, que são muito mais flexíveis. Como as skills podem ser empacotadas com scripts, algumas pessoas estão até mesmo abandonando o MCP completamente em favor de skills. Eu ainda acho que há muitos casos em que o MCP é melhor do que skills. Um dos pontos fortes do MCP em relação às skills é o gerenciamento de ciclo de vida, especialmente ao utilizá-lo sobre HTTPS. Por exemplo, como empresa, você pode implantar um servidor MCP como um serviço web e seus clientes só precisam configurá-lo uma vez para ter acesso imediato a documentação atualizada. Com skills, por outro lado, garantir que todos os seus clientes usem apenas as versões mais recentes ainda é um problema a ser resolvido em escala. ### Agent skills Uma [skill](https://agentskills.io) é um diretório contendo instruções (`SKILL.md`), scripts auxiliares opcionais e documentação que ensinam ao agente como executar um fluxo de trabalho de engenharia específico. O conceito arquitetural definidor por trás das skills é a **revelação progressiva** (*progressive disclosure*; explore padrões de produção em [O Guia Prático de Agent Skills](https://danicat.dev/pt-br/posts/20260829-the-pragmatic-guide-to-agent-skills/)). Em vez de despejar centenas de páginas de documentação na janela de contexto do agente logo de início, o sistema apenas injeta o nome e a descrição da skill. Quando o agente determina que uma tarefa corresponde a uma skill, ele carrega as instruções completas do `SKILL.md` e executa os scripts empacotados sob demanda. Esse modelo garante que o conhecimento especializado esteja sempre disponível, sem sobrecarregar a janela de contexto nem distrair seu agente com dados irrelevantes para a tarefa em mãos. Os agentes usam as descrições das skills para identificar seus gatilhos de ativação, mas depender unicamente da ativação automática é arriscado. Por padrão, o Antigravity mapeia todas as skills como comandos com barra (*slash commands*), para que você possa forçar a ativação de uma skill digitando `/` em qualquer lugar do seu prompt. Ser proativa com a ativação de skills vai lhe poupar muitas dores de cabeça se a skill for importante para o seu fluxo de trabalho. ### Hooks Enquanto regras, prompts e até mesmo skills oferecem uma orientação branda, os [**hooks**](https://danicat.dev/pt-br/posts/20260610-mastering-hooks/) introduzem controle determinístico no loop do agente. Hooks são callbacks que interceptam o ciclo de vida do agente em momentos específicos, como antes da execução de uma ferramenta (`PreToolUse`), após a execução de uma ferramenta (`PostToolUse`), antes de uma invocação do modelo (`PreInvocation`) ou no encerramento da sessão (`Stop`). Como os LLMs são não determinísticos, dizer a um agente para "sempre rodar um linter após editar o código" via prompts deixa a validação à mercê da sorte. Hooks, por outro lado, são controlados pelo harness e sempre são executados para o seu evento designado. Há apenas um alerta que você precisa sempre prestar atenção ao projetar seus hooks: os modelos são muito bons em contorná-los. Sim, infelizmente, isso acontece. Um hook pode impedir o agente de fazer algo prejudicial, apenas para o agente tentar ser mais esperto que o hook logo em seguida, seja alterando a configuração do agente, tentando mascarar a condição de disparo ou, pior, reescrevendo o script do hook. Me entristece dizer isso: no passado, eu usava bastante os hooks, mas com a nova geração de modelos eles ficaram espertos demais para o próprio bem, então estou aos poucos migrando dos hooks para as skills. Parece educar uma criança: não proíba, eduque. ### Subagentes Os [**subagentes**](https://danicat.dev/pt-br/posts/20260722-the-rise-of-the-subagents/) oferecem outra solução para o problema da janela de contexto, ao mesmo tempo em que possibilitam paradigmas interessantes, como a execução paralela. Ao criar subagentes, o agente principal pode segmentar o espaço do problema e criar um agente focado em cada tarefa. Um exemplo trivial seria trabalhar em um serviço web que possui tanto frontend quanto backend. As mudanças são essencialmente ortogonais entre si: o frontend requer HTML, CSS e JavaScript, enquanto o backend requer Go, Python e talvez algum SQL. As tarefas de frontend e backend terão padrões de código e pipelines de build diferentes. Com exceção do contrato entre eles, não há nada em comum e, se feitas na mesma janela de contexto, uma parte será apenas ruído para a outra. Ao dividir as tarefas entre dois (ou mais) subagentes, você garante que cada subagente tenha foco total na sua fatia da stack, reduzindo o risco de degradação do contexto causada por cruzar os fluxos de duas stacks de tecnologia completamente diferentes. Outro bom exemplo é quando você precisa de uma revisão imparcial do trabalho que acabou de concluir. Peça ao agente para rodar a revisão de código em um subagente e você terá o benefício de "olhos frescos" analisando o código. ### Sidecars > Nota: no momento em que escrevo, sidecars funcionam apenas no Antigravity 2.0. Eles não estão disponíveis no CLI `agy` nem na IDE. **Sidecars** são processos em segundo plano que rodam junto com o agente durante a sessão. Eles podem ser processos persistentes com ciclo de vida gerenciado pelo Antigravity por meio de uma política de reinicialização definida, ou processos que rodam de acordo com um agendamento. Eu ainda não explorei os sidecars a fundo, então não há muito que eu possa acrescentar à discussão no momento, mas meu colega Mete Atamel acabou de começar a escrever sobre eles, e encorajo você a conferir [o artigo dele](https://medium.com/google-cloud/where-does-antigravity-look-for-sidecars-20e7002b9246) para saber mais. ### Plugins de agentes Plugins são, em essência, pacotes de customizações, reunindo regras, MCPs, skills, hooks, agentes e sidecars em uma única unidade de distribuição. Venho experimentando plugins há algum tempo e, infelizmente, eles não funcionam tão suavemente quanto deveriam. Por exemplo, tive bastante trabalho tentando garantir que os hooks empacotados com meus plugins fossem executados corretamente, mas isso nunca funcionou para mim. Além disso, o suporte a plugins não é padronizado entre as diferentes superfícies: por exemplo, sidecars são suportados apenas em plugins do Antigravity 2.0, agentes customizados são suportados apenas no Antigravity CLI, e assim por diante. Para que eles servem então? MCPs e skills. Seja por coincidência ou não, é também aí que a indústria está convergindo com o novo padrão [Agent Plugins](https://agent-plugins.org/specification). A justificativa deles é que hooks, agentes, regras, servidores LSP e outros ainda são bastante específicos de cada cliente e, portanto, fora do escopo por enquanto. ## Montando o seu kit de ferramentas agentivas em Go Agora que cobrimos os harnesses de agentes e as customizações, vamos montar nosso kit de ferramentas de desenvolvimento em Go. Podemos dividi-las entre ferramentas essenciais da comunidade e extensões especializadas nativas de IA. ### Ferramentas essenciais da comunidade Embora a toolchain padrão do Go forneça aos agentes uma linha de base sólida, várias ferramentas da comunidade elevam a qualidade do código e a automação de releases: - [**`golangci-lint`**](https://golangci-lint.run/): Reúne dezenas de linters rápidos em uma única passada, capturando erros não tratados, asserções de tipo não verificadas, variáveis sombreadas e armadilhas de concorrência que o `go vet` não detecta. - [**`goreleaser`**](https://goreleaser.com/): Para projetos que distribuem binários, o `goreleaser` automatiza a compilação de artefatos multiplataforma, o gerenciamento de pipelines de release e a geração de changelogs a partir do `.goreleaser.yaml`. - [**`modernize`**](https://pkg.go.dev/golang.org/x/tools/go/analysis/passes/modernize/cmd/modernize) / **`go fix`**: Analisa o código em relação a versões mais recentes do Go e atualiza mecanicamente o boilerplate antigo, como substituir loops manuais em slices/maps ou funções auxiliares min/max por funções nativas modernas. - [**`deadcode`**](https://pkg.go.dev/golang.org/x/tools/cmd/deadcode): Usa análise de alcance de programa inteiro para identificar funções não utilizadas e código inalcançável entre os pacotes. - **`selene` e `testquery` (jabá descarado):** Eu mantenho duas ferramentas de código aberto para inspecionar e aprimorar suítes de testes. O [**`selene`**](https://github.com/danicat/selene) é uma ferramenta de testes de mutação para Go que introduz falhas direcionadas na AST para verificar se os testes realmente detectam defeitos no código. O [**`testquery`**](https://github.com/danicat/testquery) é uma CLI que expõe uma interface SQL para consultar resultados de testes e cobertura por teste em Go. Embora sejam um tanto de nicho, elas me ajudam a otimizar minhas suítes de testes. ### Ferramental específico para IA As ferramentas acima foram construídas para desenvolvedores humanos, mas agentes de código podem executá-las diretamente por meio de comandos de shell padrão. Agora, vamos ver os MCPs e skills especializados para fluxos de trabalho nativos de IA. #### O servidor MCP oficial do `gopls` Em uma IDE tradicional, o `gopls` fornece ao editor percepção semântica: verificação de tipos, referências e definições de símbolos. Mas quando um agente opera em um ambiente headless ou de terminal, ele normalmente interage com o código como arquivos de texto puro. Para fechar essa lacuna, a equipe do Go adicionou suporte nativo a MCP ao [**`gopls`**](https://pkg.go.dev/golang.org/x/tools/gopls). Executar o `gopls` no modo MCP expõe o verificador de tipos e o índice do language server diretamente ao modelo como ferramentas executáveis. Isso permite que o agente navegue pelas hierarquias de pacotes e inspecione assinaturas de tipos usando o próprio modelo semântico do compilador. #### O servidor MCP `godoctor` Uma das minhas queixas sobre o servidor MCP do `gopls` é que ele foi projetado sobre a API do LSP, que não foi pensada com agentes em mente: LSPs foram construídos para velocidade de digitação e toques interativos no teclado, enquanto os fluxos de trabalho de agentes são transacionais. Por essa razão, construí o [**`godoctor`**](https://github.com/danicat/godoctor) para fornecer ferramentas nativas de IA para desenvolvimento em Go. A versão atual do `godoctor` oferece o seguinte: - **`smart_edit`:** Um editor de arquivos ciente da AST com validação automática via `go vet` e correção de digitação. Se uma edição introduzir um erro de compilação ou sintaxe, o `godoctor` reverte automaticamente a alteração e sugere correções com base em identificadores próximos, com um prompt de orientação no estilo "você quis dizer...?". - **`smart_build`:** Um pipeline automatizado de verificação que executa a higiene do módulo (`go mod tidy`, `modernize`, `goimports`), compila o pacote, executa testes com cobertura e valida o linting via `golangci-lint` em uma única passada. - **`smart_test`:** Um pipeline de testes opinativo com suporte para `testquery` e `selene`. - **`read_docs`:** Consulta de documentação baseada em `go doc`, com suporte a exemplos e um sistema de fallback que exibe a documentação independentemente da configuração do módulo. #### Conhecimento de plataforma e autoaperfeiçoamento Enquanto `godoctor` e `gopls` lidam com a semântica do código local, agentes de programação também precisam de conhecimento em tempo real da plataforma e playbooks de fluxo de trabalho para entender os serviços com os quais estão se integrando. Aqui estão os recursos essenciais de nuvem e API para trabalhar com Go, GCP e Gemini: - **Google Developer Knowledge MCP (`developerknowledge.googleapis.com/mcp`):** Conecta o agente diretamente à documentação oficial do Google Cloud, Gemini Enterprise (Vertex AI) e APIs do Google. - **Gemini Docs MCP (`gemini-api-docs-mcp.dev`):** Fornece documentação atualizada para os endpoints da Gemini API, atualizações do SDK e padrões de configuração (leia mais no [guia de agentes de código do Gemini](https://ai.google.dev/gemini-api/docs/coding-agents)). - **Skills oficiais do Google:** [**`github.com/google/skills`**](https://github.com/google/skills) e [**`github.com/google-gemini/gemini-skills`**](https://github.com/google-gemini/gemini-skills) contêm skills oficiais mantidas pelo Google (incluindo `gemini-api-dev`, `gemini-live-api-dev` e `gemini-interactions-api`). - **Catálogo comunitário e pessoal:** Você pode explorar minhas skills pessoais em [**`skills.danicat.dev`**](https://skills.danicat.dev) (ou no [**GitHub**](https://github.com/danicat/skills)), que incluem skills para boas práticas de engenharia, desenvolvimento de jogos 2D, mídia generativa (Lyria, Nano Banana Pro) e outras. E para otimizar seu próprio fluxo de trabalho com extensões personalizadas: - **AgentSkills MCP (`agentskills.io/mcp`):** O mecanismo oficial de busca e recuperação para consultar a [especificação aberta do Agent Skills](https://agentskills.io) e boas práticas de criação. Excelente para quando você criar suas próprias skills como parte do seu trabalho diário. O que você deveria fazer. - **Skills para desenvolvimento de MCP:** Se existe um MCP para desenvolvimento de agent skills, por que não ter também [skills de agentes para desenvolvimento de MCP](https://modelcontextprotocol.io/docs/2026-07-28/develop/build-with-agent-skills)? Sim, você leu certo (rs). Embora seja um pouco nichado, como você pode ver pelo meu próprio trabalho, criar MCPs para seu próprio uso também é uma ótima maneira de aprimorar seu ambiente de desenvolvimento. ## O setup agentivo de 5 minutos do Gopher Se você deseja uma configuração opinativa para começar a trabalhar com o Gemini hoje mesmo, aqui está o guia rápido de 5 minutos: 1. Baixe o [Antigravity](https://antigravity.google) no site oficial. 2. Configure os servidores MCP recomendados: - [Gemini Docs MCP](https://ai.google.dev/gemini-api/docs/coding-agents): `npx add-mcp "https://gemini-api-docs-mcp.dev"` - [Developer Knowledge MCP](https://developers.google.com/knowledge/mcp): ative a API e configure-a seguindo as instruções na página de documentação. - [Agent Skills MCP](https://agentskills.io): expanda o botão de cópia em qualquer página para ver as instruções. - [godoctor](https://github.com/danicat/godoctor): use o script de instalação de uma linha só. 3. Adicione as skills recomendadas: - [Desenvolvimento com Gemini API](https://ai.google.dev/gemini-api/docs/coding-agents): `npx skills add google-gemini/gemini-skills --skill gemini-api-dev` - [Swarm coding](https://danicat.dev/pt-br/posts/20260722-the-rise-of-the-subagents/): `npx skills add github.com/danicat/skills/agents/swarm-coding` 4. Faça um test drive no loop: Oriente seu agente a executar uma passada de verificação autônoma: > Run a smart build on this package with godoctor, address any findings, and evaluate the test suite with selene. ## Conclusão Neste artigo, vimos por que o compilador rápido e o ferramental rigoroso do Go garantem alta afinidade agentiva, como as primitivas de customização (regras, MCP, skills, hooks, subagentes e plugins) se encaixam e como configurar um ambiente prático para Go no Antigravity. Depois que o seu fluxo de trabalho estiver ajustado, você pode atravessar para o outro lado da mesa e construir seus próprios agentes autônomos em Go: * Leia [**Construindo Agentes de IA em Go: GenAI SDK, Genkit e ADK**](https://danicat.dev/pt-br/posts/20260825-gemini-for-go-developers-part-3-building-agents/) para uma comparação lado a lado das três principais arquiteturas de agentes em Go. * Leia [**Por Que Reescrevi Meu Agente RAG em Python Usando Go e Genkit**](https://danicat.dev/pt-br/posts/20261006-building-agentic-backends-with-genkit-go/) para ver como Agent Skills e delegação multiagente funcionam dentro de um binário Go compilado. --- # A Família de Modelos Gemini: Um Guia Prático para Gophers - **Markdown URL**: https://danicat.dev/pt-br/posts/gemini-for-go-developers-part-1-model-family/index.md - **HTML URL**: https://danicat.dev/pt-br/posts/gemini-for-go-developers-part-1-model-family/ - **Date**: 2026-08-08 - **Categories**: Applied GenAI - **Tags**: gemini, genai-sdk, golang, llm, multimodal - **Series**: Gemini for Go Developers - **Summary**: Compare os modelos Gemini 3.x Pro, Flash, Flash-Lite e Nano Banana, ajuste níveis de raciocínio e escreva aplicações multimodais em Go com o GenAI SDK oficial. Construir software confiável potencializado por IA em Go começa por entender os modelos por baixo do capô. Antes de recorrer a frameworks de agentes de alto nível ou orquestrar fluxos de múltiplas etapas, você precisa saber qual modelo se adapta à sua carga de trabalho, como ajustar seu orçamento de raciocínio e qual superfície de API combina com a sua arquitetura. Neste guia, vamos explorar a família de modelos Gemini, detalhar parâmetros essenciais de configuração como níveis de *thinking* e escrever nossa primeira aplicação multimodal usando o [Go GenAI SDK](https://pkg.go.dev/google.golang.org/genai) oficial. ## A família de modelos Gemini Geralmente tratamos "Gemini" como um nome único para os produtos de IA do Google, muito parecido com usar "Google" como sinônimo de busca. Na realidade, o Gemini é uma família de modelos distintos, construídos para diferentes compensações operacionais (*trade-offs*). Embora os modelos de fronteira sejam os que ganham as manchetes, saber quando usar modelos menores ou especializados é essencial para uma engenharia com bom custo-benefício. A seleção do modelo também [influencia diretamente a experiência do usuário](https://services.google.com/fh/files/blogs/google_delayexp.pdf) e a adoção do produto, já que a latência varia bruscamente entre os diferentes tiers de modelos. Recorrer a um modelo Gemini Pro com níveis altos de *thinking* para cada tarefa é tentador, mas nem sempre é a escolha certa. Em muitos casos, isso apenas aumenta a latência por requisição e os custos de API sem entregar um resultado melhor. ### Esquema de nomenclatura dos modelos Para navegar pelo catálogo do Gemini, ajuda entender como o Google nomeia seus modelos. Uma string de modelo padrão segue este padrão: \[ \text{[family]}-\text{[version]}-\text{[tier]}{-\text{[modifier]}} \] Por exemplo: `gemini-3.7-flash` ou `gemini-3-pro-image`. **Família (Family)**: Embora a maioria dos modelos pertença à família Gemini, o Google também possui outras famílias de modelos, como Veo e Lyria. **Números de Versão (Version Numbers)**: Representam saltos geracionais em inteligência, manipulação da janela de contexto e aderência a instruções. **Tiers de Modelo (Model Tiers)**: * **Pro**: Projetado para raciocínio complexo em múltiplas etapas. * **Flash**: Modelo balanceado com foco em velocidade. * **Flash-Lite**: Otimizado para velocidade e tarefas simples de alto rendimento (*throughput*). **Modificadores (Modifiers)**: Podem indicar uma subfamília ou especialização, como `image` em `gemini-3.1-flash-image` ou `live` em `gemini-3.1-flash-live-preview`. Também podem incluir modificadores de ciclo de vida como `-preview` ou `-exp` (para experimental). ### Visão geral dos modelos Aqui está uma visão geral dos principais modelos Gemini, começando com a linha de modelos de fronteira Gemini 3.x: #### Gemini 3.x O Gemini 3.x é a principal linha de modelos de fronteira, disponível nos tiers Pro, Flash e Flash-Lite. Esses modelos de uso geral também são a escolha principal para geração de código e tarefas de engenharia de software. Os modelos atuais incluem: - `gemini-3.6-flash`: O cavalo de batalha veloz para raciocínio multimodal e tarefas agentivas - `gemini-3.5-flash-lite`: O tier mais barato e ultrarrápido para microsserviços de alto throughput - `gemini-3.1-pro-preview`: Tier avançado para raciocínio complexo em múltiplas etapas e análise profunda de bases de código #### Modelos de imagem Gemini (Nano Banana) Embora tecnicamente ainda faça parte da família Gemini, este é um modelo especializado para geração de imagens, fornecendo tanto entrada quanto saída multimodais (imagem e texto). Ele é capaz de produzir imagens do zero e realizar edições em imagens existentes. Os modelos atuais incluem: - `gemini-2.5-flash-image` (também conhecido como Nano Banana) - `gemini-3-pro-image` (também conhecido como Nano Banana Pro) - `gemini-3.1-flash-image` (também conhecido como Nano Banana 2) - `gemini-3.1-flash-lite-image` (também conhecido como Nano Banana 2 Lite) #### Veo Um modelo especializado em [geração de vídeo com áudio nativo](https://ai.google.dev/gemini-api/docs/veo). Os vídeos são gerados com base em prompts de texto e imagens-chave para marcar transições (quadro inicial e final) e como referências. O Veo 3.1 gera clipes de até 8 segundos, mas é possível estendê-los em até 20 vezes em incrementos de 7 segundos. Modelos atuais: - `veo-3.1-generate-preview` - `veo-3.1-lite-generate-preview` (geração rápida) #### Lyria O [Lyria](https://ai.google.dev/gemini-api/docs/music-generation) é especializado em geração de música, entregando tanto composições instrumentais quanto vocais. O Lyria aceita texto e imagens como entrada, com as imagens servindo de inspiração para a composição. Você também pode fornecer a letra por conta própria ou deixar que o modelo a crie para você. Modelos atuais: - `lyria-3-pro-preview` - `lyria-3-clip-preview` (clipes curtos de 30s) #### Gemma O [Gemma](https://ai.google.dev/gemma/docs) é uma família de modelos de pesos abertos (*open-weights*) do Google. Ele é treinado com a mesma tecnologia por trás do Gemini, mas projetado para ser executado na sua própria infraestrutura. Além dos modelos oferecidos pelo Google, o Gemma também conta com uma [comunidade forte](https://deepmind.google/models/gemma/gemmaverse/) que produz versões com ajuste fino (*fine-tuned*) para todo tipo de caso de uso. Alguns modelos Gemma são pequenos o suficiente para serem executados em máquinas locais, viabilizando casos de uso em que a conectividade de rede é limitada ou inexistente. Os modelos maiores são muito capazes, atendendo a casos de uso onde soberania e isolamento de rede são necessários. #### Menções notáveis - Modelos Live: Enquanto os modelos anteriores lidam com tarefas em lote ou requisição-resposta, o Google também oferece modelos live para streaming em tempo real. Procure por `-live` no nome (por exemplo, `gemini-3.1-flash-live-preview`). - Text-to-speech: Gera fala a partir de texto com controle de narração usando tags de áudio (`gemini-3.1-flash-tts-preview`). - Computer use: Um modelo que pode "enxergar" a tela e automatizar tarefas no navegador (`gemini-2.5-computer-use-preview-10-2025`). Como você pode ver, o Gemini é muito mais do que um único modelo. É uma suíte completa cobrindo tudo, desde chatbots básicos até criação multimodal e recursos agentivos. Para especificações detalhadas de cada modelo, consulte a [documentação oficial dos modelos Gemini](https://ai.google.dev/gemini-api/docs/models). ## Recursos adicionais Além da completude padrão de texto, os modelos Gemini suportam recursos adicionais para a construção de aplicações complexas. Aqui estão alguns dos mais importantes. ### Thinking Os modelos Gemini 2.5 e posteriores utilizam um processo interno de raciocínio que aprimora significativamente as capacidades de planejamento em múltiplas etapas, lógica, programação e matemática. Antes de gerar a resposta final, o modelo raciocina internamente gerando "tokens de pensamento" (*thinking tokens*) para analisar casos extremos (*edge cases*) e planejar estratégias em múltiplos passos. O *thinking* é um recurso que pode ser controlado usando os parâmetros de configuração `thinking budget` no 2.5 e `thinking level` no 3.x. Quanto maior o *budget* ou o *thinking level*, mais tempo e tokens o modelo gastará durante a fase de raciocínio. Quando o *thinking* está ativo, o total de tokens de saída faturáveis inclui tanto o texto de saída gerado quanto os tokens de pensamento gerados pelo modelo. Ajustar o nível de *thinking* com base na complexidade da tarefa é um passo fundamental para serviços em produção. ### Ferramentas integradas e chamadas de função (*function calling*) O *function calling* permite que os modelos Gemini se comuniquem com ferramentas externas, APIs e bancos de dados. O Gemini suporta tanto ferramentas integradas (como `google_search` e `code_execution`) quanto funções customizadas definidas no nível da aplicação. O *function calling* tem três casos de uso principais: - **Executar ações:** Interagir com sistemas externos via APIs, como agendar reuniões, enviar e-mails, criar faturas ou controlar dispositivos de casa inteligente. - **Aumentar conhecimento:** Buscar informações em tempo real ou privadas a partir de bancos de dados externos, microsserviços e bases de conhecimento. - **Estender capacidades:** Realizar cálculos matemáticos precisos, conversão de dados ou geração de gráficos que superam os limites do LLM. #### Como o *function calling* funciona O *function calling* segue um processo de execução em 4 etapas entre sua aplicação e o modelo: 1. **Declarar ferramentas**: Defina as declarações das funções (nome, descrição clara e JSON Schemas dos parâmetros) e passe-as na configuração da requisição. 2. **Modelo identifica a intenção da ferramenta**: O modelo inspeciona o prompt e as declarações de ferramentas. Se uma ferramenta for necessária, ele retorna uma intenção estruturada de chamada de ferramenta contendo o nome da função e os argumentos. 3. **Executar o código da função**: O modelo *não* executa o código diretamente. Sua aplicação recebe a requisição de chamada de função, executa a lógica local correspondente e captura o resultado. 4. **Retornar o resultado da função**: Envie a saída da execução de volta ao modelo como uma etapa de resultado da função. O modelo usa esses dados para gerar sua resposta final em linguagem natural ou decidir se chamadas adicionais de ferramentas são necessárias. ### Saídas estruturadas (*structured outputs*) Você pode configurar os modelos Gemini para gerar respostas que sigam rigorosamente um [JSON Schema](https://ai.google.dev/gemini-api/docs/structured-output) fornecido. Isso simplifica a extração de dados estruturados a partir de texto, eliminando rotinas frágeis de parsing ao converter respostas do modelo em estruturas de dados. Além de escrever JSON Schemas brutos em payloads REST, os SDKs do Google GenAI permitem que desenvolvedores definam schemas usando construções nativas da linguagem, como [Pydantic](https://docs.pydantic.dev/) em Python e struct tags em Go. ## Consumindo modelos programaticamente Com o ecossistema de modelos e suas capacidades cobertos, vamos ver como incorporar essas APIs em aplicações Go. Assim como modelos diferentes existem para casos de uso diferentes, várias superfícies de API estão disponíveis. Vamos começar com a mais básica: Generate Content. ### Generate Content API Esta é a [API generativa](https://ai.google.dev/api/generate-content#method:-models.generatecontent) mais básica. Trata-se de uma interface stateless (sem estado) que aceita uma única requisição e retorna uma resposta. Para conversas de múltiplos turnos, sua aplicação precisa enviar todo o histórico de chat a cada chamada. Isso exige gerenciar ativamente o histórico de conversa para ficar dentro dos limites da janela de contexto. As aplicações normalmente resumem o histórico assim que ele atinge um determinado limite. Para reduzir custos de entrada em sessões longas, a Gemini API suporta [caching implícito](https://ai.google.dev/gemini-api/docs/caching) para todos os modelos a partir do Gemini 2.5, além de [caching explícito](https://ai.google.dev/gemini-api/docs/generate-content/caching) para payloads pesados. Embora a Generate Content API seja boa para gerações stateless simples, ela está sendo gradualmente substituída pela mais recente e mais poderosa Interactions API. ### Interactions API > Nota: Até o momento, a Interactions API ainda não é suportada pelo Go GenAI SDK oficial. O progresso da implementação está sendo acompanhado nesta [issue do GitHub](https://github.com/googleapis/go-genai/issues/658). A [Interactions API](https://ai.google.dev/gemini-api/docs/interactions-overview) é a interface unificada do Google projetada para todas as tarefas, desde chat simples e uso de ferramentas até fluxos de trabalho agentivos complexos. Ela pode gerenciar o histórico de conversas no lado do servidor, de modo que sua aplicação não precise fazer isso. ### Live API A [Live API](https://ai.google.dev/gemini-api/docs/live-api) permite conversas de voz e vídeo bidirecionais em tempo real via WebSockets. Ela detecta automaticamente quando o usuário fala ou interrompe, fazendo com que as interações de voz pareçam naturais, enquanto suporta ferramentas como busca na web e function calling diretamente na sessão ao vivo. ### Batch API A [Batch API](https://ai.google.dev/gemini-api/docs/batch-api) permite processar grandes volumes de dados de forma assíncrona pela metade do preço. Os jobs são executados em segundo plano durante horários de menor movimento (geralmente concluídos em até 24 horas), tornando-a ideal para cargas de trabalho não urgentes. ### Managed Agents API Os [agentes gerenciados](https://ai.google.dev/gemini-api/docs/agents) oferecem um ambiente de execução totalmente hospedado onde agentes de IA planejam e executam tarefas de forma autônoma. Uma única chamada de API provisiona um sandbox Linux isolado no nível do SO com runtimes pré-instalados como Python e Node, permitindo que o agente execute código, gerencie arquivos e navegue na web. O Google fornece dois agentes gerenciados prontos para uso: - **Antigravity Agent** (`antigravity-preview-05-2026`): O agente padrão de propósito geral equipado com o Gemini 3.6 Flash (configurável para Gemini 3.5 Flash ou Flash-Lite) para execução de código, gerenciamento de arquivos e acesso à web. - **Deep Research Agent** (`deep-research-preview-04-2026`): Um agente de pesquisa autônomo que consulta dados da web de múltiplas fontes e compila relatórios detalhados de pesquisa em segundo plano. Você também pode estender o agente Antigravity definindo regras de sistema inline ou montando um arquivo `AGENTS.md`, anexando diretórios de skills estruturados (`SKILL.md`) ou montando arquivos locais, buckets do Cloud Storage e repositórios Git diretamente no workspace remoto (`/workspace`). ## Acesso e faturamento Ao integrar o Gemini, o Google oferece dois modos principais de acesso e faturamento, dependendo das suas necessidades: 1. **Google AI Studio (Google AI)**: Roteia requisições através da Gemini API usando uma chave de API (`GEMINI_API_KEY` ou `GOOGLE_API_KEY`). Ideal para prototipagem, projetos pessoais, aplicações indie e onboarding rápido para desenvolvedores. 2. **Gemini Enterprise (anteriormente Vertex AI)**: Roteia requisições através de endpoints do Google Cloud usando Google Cloud IAM, Application Default Credentials (ADC), chaves de conta de serviço ou tokens de usuário OAuth 2.0. Ideal para cargas de trabalho empresariais em produção que exigem rigorosa privacidade de dados, conformidade de segurança, SLAs, gerenciamento de recursos do GCP e descontos por uso contínuo (*committed-use discounts*). ## Go GenAI SDK Agora vamos ver como isso funciona em código Go. O SDK oficial para integrar o Gemini em aplicações Go é o [`google.golang.org/genai`](https://pkg.go.dev/google.golang.org/genai). Às vezes você o verá sendo chamado de SDK "unificado", pois foi projetado para suportar todos os modelos do Google e autenticação tanto via Google AI quanto pelo Gemini Enterprise. Ele substitui o pacote legado `github.com/google/generative-ai-go`, que foi descontinuado (*deprecated*). Instale-o com `go get`: ```bash go get google.golang.org/genai ``` Aqui está um exemplo usando autenticação e faturamento do Gemini Enterprise: ```go package main import ( "context" "fmt" "log" "os" "google.golang.org/genai" ) func main() { ctx := context.Background() // Initialize the client for Gemini Enterprise (Vertex AI) client, err := genai.NewClient(ctx, &genai.ClientConfig{ Project: os.Getenv("GOOGLE_CLOUD_PROJECT"), Location: "global", // Nano Banana models are served globally Backend: genai.BackendEnterprise, }) if err != nil { log.Fatalf("failed to create GenAI client: %v", err) } prompt := "Generate a high-resolution, cute image of a fluffy cat wearing a tiny wizard hat." // Call Nano Banana 2 Lite (gemini-3.1-flash-lite-image) resp, err := client.Models.GenerateContent( ctx, "gemini-3.1-flash-lite-image", genai.Text(prompt), nil, ) if err != nil { log.Fatalf("failed to generate image: %v", err) } // Extract generated image bytes from response parts for _, candidate := range resp.Candidates { if candidate.Content == nil { continue } for _, part := range candidate.Content.Parts { if part.InlineData != nil && part.InlineData.Data != nil { filename := "cute_cat.png" if err := os.WriteFile(filename, part.InlineData.Data, 0644); err != nil { log.Fatalf("failed to save image: %v", err) } fmt.Printf("Successfully generated and saved cat picture to %s!\n", filename) return } } } log.Fatal("no image data returned in response") } ``` Para executar este exemplo, você precisará de um projeto no Google Cloud com a API AI Platform habilitada. Por exemplo: ```sh export GOOGLE_CLOUD_PROJECT="your-project-id-goes-here" go run main.go ``` Aqui está o resultado: ![Saída de imagem de gato mago gerada no terminal Go](image.png "O verdadeiro propósito da IA: geração infinita de fotos de gatinhos") Embora este seja um exemplo simples para mostrar como o SDK de baixo nível funciona, essas mesmas primitivas alimentam desde ferramentas locais de CLI até runtimes de agentes em produção. ## Para onde ir a partir daqui Agora que você já conhece o catálogo de modelos Gemini e o Go GenAI SDK, há dois caminhos naturais para explorar a seguir: * Se você quiser usar o Gemini para escrever, verificar e refatorar código Go mais rápido, confira [**Por Que Go Foi Feito para Agentes de Código (E Como Ajustar o Seu)**](https://danicat.dev/pt-br/posts/20260817-gemini-for-go-developers-part-2-coding-with-gemini/). * Se você estiver pronto para construir agentes com chamada de ferramentas e comparar o Go GenAI SDK com frameworks de nível mais alto como [Genkit](https://genkit.dev/) e [ADK](https://adk.dev/), vá direto para [**Construindo Agentes de IA em Go: GenAI SDK, Genkit e ADK**](https://danicat.dev/pt-br/posts/20260825-gemini-for-go-developers-part-3-building-agents/). --- # IA Generativa e Arte: É Realmente Apenas Slop? - **Markdown URL**: https://danicat.dev/pt-br/posts/genai-and-art-is-it-really-just-slop/index.md - **HTML URL**: https://danicat.dev/pt-br/posts/genai-and-art-is-it-really-just-slop/ - **Date**: 2026-07-27 - **Categories**: Perspectives - **Tags**: ai, art - **Summary**: Um ensaio sobre a arte gerada por IA: é realmente apenas slop? Uma análise sobre gatekeeping, paralelos históricos, linguagem de domínio e intenção. "Slop" é o insulto que define o atual ciclo da tecnologia. É uma palavra útil. Ela descreve o cansaço de abrir um feed lotado de conteúdo preguiçoso, gerado ao digitar três palavras numa caixa de prompt e publicar o resultado sem nenhum tipo de revisão. Mas será que ela deveria ser usada para rotular toda uma nova categoria de arte sem critério algum? ## Um aviso: este ensaio não é sobre treinamento de IA Antes de começarmos, vamos tirar o elefante da sala: não vou debater dados de treinamento, web scraping ou leis de direitos autorais aqui. Não sou qualificada para entrar nesses assuntos e nem vou tentar. ![Diagrama da Fase de Treinamento vs Camada de Inferência](https://danicat.dev/pt-br/posts/genai-and-art-is-it-really-just-slop/you_are_not_here.png) Por outro lado, sou pragmática. Esses modelos existem e vieram para ficar. As pessoas vão usá-los, queiramos nós ou não. Então, vamos deixar a discussão acima para os especialistas e focar no que está à nossa frente: inferência, ou em outras palavras, o processo de gerar arte por meio de prompts. ## A natureza cíclica da tecnologia e da arte Você já parou para pensar que, ao estudar a História do mundo, muitas vezes estudamos a História da Arte em paralelo? Toda grande revolução tecnológica cria uma nova maneira de fazer arte. Vimos essas ondas acontecerem com o surgimento da imprensa, da fotografia e dos computadores, só para citar alguns exemplos. Coincidentemente, toda nova forma de arte foi duramente criticada pela sua própria "velha guarda". Na Holanda do século XVII, os realistas holandeses pintavam o cotidiano: comerciantes, cenas de taverna e cozinhas pacatas. Os pintores clássicos desdenhavam disso como algo barato, comercial e preguiçoso. Durante a Revolução Industrial, os neoclássicos ridicularizavam o estilo emotivo do Romantismo como "delírio febril". No entanto, ambos os movimentos encheram museus e se tornaram o padrão tradicional contra o qual os artistas do futuro tiveram que lutar. A revolta em torno da arte de IA se encaixa perfeitamente nesse mesmo padrão. O que vemos hoje é um novo meio colidindo com velhas noções de artesanato e técnica. ## Slop para alguns, arte para outros Para começar, nunca precisamos de IA para criar "slop". Muito antes dos modelos de texto para imagem existirem, a sociedade já discutia onde a arte termina e o ruído de baixo esforço começa. A questão principal é: onde traçar a linha? Vamos dar uma olhada em algumas obras de arte e em seus contrapontos. ### Retrato clássico vs. pop art produzida em massa | *Mona Lisa* (Leonardo da Vinci) | *Mona Cat* (Romero Britto) | | :---: | :---: | | ![Mona Lisa de Leonardo Da Vinci](https://danicat.dev/pt-br/posts/genai-and-art-is-it-really-just-slop/mona_lisa.jpg) | ![Mona Cat de Romero Britto](https://danicat.dev/pt-br/posts/genai-and-art-is-it-really-just-slop/mona_cat.png) | A *Mona Lisa* de Da Vinci é universalmente aceita como bela-arte, mas e a *Mona Cat* de Romero Britto? Os críticos frequentemente chamam a obra de Britto de slop comercial devido ao uso de métodos industriais e produção em massa, além de ser derivativa. Na sua opinião, a crítica é válida ou é apenas uma questão de gosto? Não existe resposta certa para essa pergunta, já que a arte dele evoca sentimentos diferentes em pessoas diferentes. ### Intenção vs. execução | *Ecce Homo* (Elías García Martínez) | *Ecce Homo "Restaurado"* (Cecilia Giménez) | | :---: | :---: | | ![Ecce Homo Original](https://danicat.dev/pt-br/posts/genai-and-art-is-it-really-just-slop/ecce_homo_original.jpg) | ![Ecce Homo Restaurado](https://danicat.dev/pt-br/posts/genai-and-art-is-it-really-just-slop/ecce_homo_restored.jpg) | O que a maioria consideraria uma tentativa desastrada de restaurar um afresco deteriorado acabou se tornando um acidente feliz para a pequena comunidade de Borja, na Espanha. Graças à internet, o interesse pelo [Ecce Homo](https://pt.wikipedia.org/wiki/Ecce_Homo_de_Borja) (pintado por Elías García Martínez por volta de 1930) cresceu exponencialmente, transformando-se em um fenômeno da cultura pop e destino turístico internacional. Este é um caso em que a visão da artista-restauradora amadora Cecilia Giménez não correspondeu à sua técnica, mas a tentativa em si virou arte, mesmo que a intenção original dela não fosse tomar essa forma. | *Girl with Balloon* (Banksy) | *Love is in the Bin* (Banksy) | | :---: | :---: | | ![Girl with Balloon de Banksy](https://danicat.dev/pt-br/posts/genai-and-art-is-it-really-just-slop/banksy_girl_with_balloon.png) | ![Love is in the Bin de Banksy](https://danicat.dev/pt-br/posts/genai-and-art-is-it-really-just-slop/banksy_love_is_in_the_bin.jpg) | Outro caso famoso em que intenção e execução não coincidiram, mas acabaram criando uma nova obra de arte mesmo assim, é "Love is in the Bin", de Banksy. O que supostamente era um mecanismo de segurança para destruir sua pintura caso ela fosse a leilão falhou no meio do processo de trituração, deixando "Girl with Balloon" apenas parcialmente destruída. Toda a experiência de assistir a uma obra de arte que tinha acabado de ser vendida por mais de um milhão de libras ser picotada em pedaços poderia ser considerada uma expressão artística por si só. Da última vez em que foi vendida, ela alcançou mais de 18 milhões de libras! ### Banana de galeria vs. kiwi esquecido | *Comedian* (Maurizio Cattelan) | *Forgotten Kiwi* (Daniela Petruzalek) | | :---: | :---: | | ![Comedian de Maurizio Catellan](https://danicat.dev/pt-br/posts/genai-and-art-is-it-really-just-slop/maurizio_catellan_comedian_banana.jpg) | ![Forgotten Kiwi](https://danicat.dev/pt-br/posts/genai-and-art-is-it-really-just-slop/forgotten_kiwi.jpg) | Este talvez seja o principal exemplo de arte de baixo esforço do nosso tempo, mas que ainda assim alcançou US$ 120.000 em um leilão. Maurizio Cattelan colou uma banana com fita adesiva na parede de uma galeria. E o comprador? Comeu a banana. Tudo leva a crer que se trata apenas de uma banana superfaturada, se não fosse pelo contexto e pelo título da obra: "Comedian". Algumas pessoas vão enxergá-la como slop; outras, como uma crítica ao consumismo ou talvez até à própria arte. Por que *Comedian* é uma obra de arte, mas este kiwi que esqueci no fundo da geladeira não é? (Não se preocupe: assim como o comprador de *Comedian*, eu comi o kiwi depois de tirar esta foto.) O fato é que a arte nunca foi definida unicamente pelo trabalho manual exigido ou pelo objeto físico em si. Ela depende de contexto, intenção e da reação do público. Sem nenhuma ironia: agora que o meu *Forgotten Kiwi* foi apresentado em palestras de conferências e neste blog, talvez ele tenha, de fato, se tornado arte. ## Não devemos fazer gatekeeping com a arte Quando um novo meio surge, os guardiões (gatekeepers) tentam definir o que conta como "arte de verdade". Quase sempre amarram suas definições ao trabalho físico, à destreza manual ou a ferramentas tradicionais. Quando a fotografia surgiu no século XIX, os pintores a rejeitaram. Alegavam que apertar um botão era rápido demais para ser arte, já que não exigia nenhuma habilidade com o pincel. Levou décadas para que a fotografia fosse reconhecida como arte. ![Eyes of the Night por Jade Gosrani](https://danicat.dev/pt-br/posts/genai-and-art-is-it-really-just-slop/eyes_of_the_night_leopard.jpg) Apertar um botão leva um segundo. Mas capturar uma imagem como *Eyes of the Night*, de Jade Gosrani, exige dias rastreando a vida selvagem, dominando a luz e esperando pacientemente. A arte está na visão, no preparo e na intenção, não no clique físico. Nós já separamos a visão da execução manual em outras áreas: ![Frieren Concert in London](frieren_orchestra_london.jpg "Concerto de Frieren em Londres. Foto por https://x.com/AnimeBlurayUK") Na música, creditamos a trilha sonora de *Frieren* ao compositor Evan Call. Em um concerto ao vivo, Call não toca todos os instrumentos. Dezenas de músicos tocam sob a regência de um maestro, enquanto o compositor fornece a visão, a estrutura e a partitura. | *Akira* (Katsuhiro Otomo, 1988) | *Titanic* (James Cameron, 1997) | | :---: | :---: | | ![Akira 1988](https://danicat.dev/pt-br/posts/genai-and-art-is-it-really-just-slop/akira_1988_bike_slide.jpg) | ![Titanic Bastidores](https://danicat.dev/pt-br/posts/genai-and-art-is-it-really-just-slop/titanic_behind_the_scenes_green_screen.jpg) | No cinema, *Akira*, de Katsuhiro Otomo, utiliza mais de 160.000 quadros desenhados à mão. Otomo não desenhou cada quadro pessoalmente. Um estúdio inteiro executou a sua visão. Em *Titanic*, de James Cameron, dezenas de profissionais da equipe construíram maquetes e operaram efeitos visuais. Nós creditamos o diretor, que molda a história, e não cada técnico no set. A Wikipédia oferece esta definição abrangente de [arte](https://pt.wikipedia.org/wiki/Arte): > *"A arte é uma gama diversificada de atividades humanas centradas em obras que utilizam talentos criativos ou imaginativos, que se espera que evoquem uma experiência válida, geralmente através da expressão de poder emocional, ideias conceituais, proficiência técnica ou beleza."* Curiosamente, o parágrafo logo a seguir admite que nem mesmo essa definição é um consenso: > *"Não há uma definição universalmente aceita do que constitui a arte, e sua interpretação variou amplamente ao longo da história e entre diferentes culturas."* Se nem a enciclopédia consegue definir arte, o gatekeeping fracassa, porque habilidade não é apenas trabalho braçal. Ela se move para a direção criativa. ## Uma definição pragmática de slop Acredito que o slop é produzido quando falta visão, esforço e/ou intenção. É a versão fast food da produção de conteúdo. Para quem tem inclinação para a matemática, gosto de dizer que **o slop é inversamente proporcional à inspiração**: \[ \text{SLOP} \propto \frac{1}{\text{INSPIRAÇÃO}} \] Inspiração é pegar uma ideia na sua cabeça e transformá-la em algo que se conecta com as outras pessoas. Se você traz zero inspiração para uma ferramenta, o que você obtém é slop, não importa o quão bom o modelo seja. Compare um prompt preguiçoso de duas palavras como `anime girl` com este prompt, que explora cuidadosamente a linguagem de domínio da criação de imagens para materializar uma visão: ```text Character: teenage girl with pink hair, shoulder length, wavy. Blue eyes, gentle smile. School uniform, white blouse, tartan skirt. Scenario: classroom, back seat, close to the window. Action: girl is sitting at her desk, book open in front of her, but she is looking at the window, holding her chin with her left hand, melancholic look, pensive. Style: anime, watercolor, 90's, pastel tones, shoujo Environment: midday light, natural light from the window, artificial light from the ceiling Camera: low angle, profile picture three-quarter view ``` E o resultado: ![Cena de Sala de Aula de Anime dos Anos 90](https://danicat.dev/pt-br/posts/genai-and-art-is-it-really-just-slop/anime_girl_classroom_90s_shoujo.jpg) O segundo prompt se apoia no conhecimento de estilos de arte, enquadramento, iluminação e composição. O resultado varia ligeiramente a cada execução, mas o clima, o enquadramento e a direção permanecem consistentes porque o prompt fornece uma orientação clara. Para guiar um modelo com eficácia, seja gerando imagens ou escrevendo código, você precisa dominar a **linguagem de domínio** do seu meio. Para imagens cinematográficas, você precisa entender de ângulos de câmera e iluminação (*low angle*, *three-quarter view*, *rim light*). Para ilustrações, precisa conhecer estilos artísticos (*90s shoujo*, *watercolor*). ![Guia de Estrutura de Prompt](https://danicat.dev/pt-br/posts/genai-and-art-is-it-really-just-slop/prompt_structure_guide_kantakanta.jpg) Um prompt estruturado (como este framework de [@kantakanta1233](https://x.com/kantakanta1233)) cobre dez áreas específicas: estilo, tema, características, vestimenta, pose, composição, cenário, iluminação, acabamento e palavras-chave negativas. Construir um prompt como esse é direção criativa, e mesmo após criar o prompt, haverá várias execuções e ajustes antes de se chegar ao resultado final. Tudo isso faz parte do processo criativo. ## Ter um bom prompt não é suficiente: ele precisa ser o seu prompt Essa questão surgiu durante a minha palestra no GDG Community Summit e achei que valia a pena mencioná-la aqui. Digamos que alguém gaste todo esse tempo e inspiração criando o prompt perfeito, e aí outra pessoa vai lá, copia esse prompt e o roda com o mínimo de esforço. Isso é arte? Nesse caso em particular, não é diferente de falsificar uma pintura ou de copiar e colar do Stack Overflow. A pessoa que apenas pegou o prompt e o executou é uma "copiadora" (copycat). É claro que é difícil rastrear a procedência de imagens na internet para saber se foram criadas pelo autor ou por alguém que só copiou o prompt. No fim das contas, a confiança e a reputação também desempenharão um papel fundamental em determinar o que é arte e o que é uma cópia de baixo esforço. ## O paralelo com a engenharia de software Caso você ainda não tenha reparado, tudo o que está sendo discutido aqui sobre arte também se aplica à nossa área de engenharia de software. Embora muita gente já tenha adotado o desenvolvimento com IA, muitas pessoas ainda o descartam como o slop do "vibe coding". Há uma forte resistência porque as pessoas acreditam que isso está arruinando a engenharia de software como carreira. Para mim, a IA é justamente o oposto. Ela não só me ajuda a automatizar as partes chatas do meu trabalho (não aguento mais escrever outra API CRUD na vida!), como também está me permitindo fazer coisas que eu não conseguiria fazer sozinha de outra forma. Por exemplo, todo o motivo de eu ter entrado na área de engenharia foi porque eu queria desenvolver jogos, um sonho que nunca se concretizou (mas sem arrependimentos aí). Agora, com o suporte de IA, a barreira de entrada ficou tão mais baixa que posso me divertir criando meus jogos mesmo com meu tempo e habilidades limitados. Não sei compor música sozinha, mas com o apoio do Gemini e do Lyria 3 consigo direcionar o modelo para a minha visão. Não tenho paciência para desenhar cada sprite e asset, mas com o Nano Banana posso guiar o modelo para gerar os assets de que preciso, e ter uma demo jogável em questão de horas em vez de dias ou semanas. Engenharia nunca foi só sobre digitar código num editor, da mesma forma que arte nunca foi só sobre segurar um pincel. À medida que as ferramentas de IA assumem mais do trabalho mecânico, como renderizar pixels ou escrever boilerplate de código em Go, nosso papel sobe na stack: vai para visão, arquitetura, conhecimento de domínio e intenção. Esse pânico não é o fim da criatividade humana. É uma fase bastante familiar de um ciclo que já vimos com a imprensa, a fotografia e os compiladores. A reação inicial tenta proteger as velhas formas de trabalhar. Com o tempo, a resistência se dissipa, as pessoas adotam as ferramentas e o meio se normaliza. O que os críticos rejeitam hoje como "slop" se tornará a ferramenta padrão de amanhã — e, com o tempo, o velho padrão que os futuros criadores tentarão mudar. O que você acha? A arte de IA está passando por uma fase temporária de atrito ou você enxerga isso de forma diferente? --- # A Ascensão dos Subagentes - **Markdown URL**: https://danicat.dev/pt-br/posts/the-rise-of-the-subagents/index.md - **HTML URL**: https://danicat.dev/pt-br/posts/the-rise-of-the-subagents/ - **Date**: 2026-07-22 - **Categories**: Agentic Coding - **Tags**: agile, antigravity, subagents - **Summary**: Entenda a evolução dos subagentes no Antigravity e aprenda a orquestrar enxames autônomos de código em Go com a skill Swarm Coding. Devo confessar que, quando li pela primeira vez sobre **subagentes**, fiquei um pouco cética. Eu entendia perfeitamente os benefícios de rodar tarefas em janelas de contexto separadas, mas nunca tinha me ocorrido disparar dezenas ou centenas de agentes em paralelo. Ou, para ser mais precisa: eu não via vantagem prática nisso. Gerenciar dois ou três agentes de código em segundo plano já consome uma fatia enorme da minha capacidade mental. Costumo paralelizar tarefas apenas quando sei que um agente está ocupado com uma operação demorada. Se coordenar três agentes assim já é exaustivo, como eu poderia sonhar em gerenciar centenas deles? Levei algum tempo para encontrar a resposta, e ela é surpreendentemente simples: **você não gerencia**! Você delega a responsabilidade de coordenar os subagentes a um próprio agente. Todo problema em computação é resolvido adicionando uma nova camada de abstração, certo? Desta vez não é diferente. Neste artigo, quero compartilhar como acompanhei a evolução do paradigma de subagentes ao longo dos últimos doze meses, consolidando essa experiência em uma skill que chamo de **Swarm Coding**. Se você está aqui pelo resumo direto (TL;DR), pode pular a seção histórica e ir direto para a definição da skill e suas explicações no final do texto. ## Uma breve (e incompleta) linha do tempo da evolução dos subagentes Subagentes não são novidade. Eu já usava esse recurso muito antes de o termo "subagente" virar moda, encapsulando chamadas a modelos como ferramentas MCP. Por exemplo, as primeiras versões do [**GoDoctor**](https://danicat.dev/pt-br/posts/20250729-how-to-build-an-mcp-server-with-gemini-cli-and-go/) incluíam uma ferramenta `code_review`, que nada mais era do que uma chamada ao Gemini com um prompt especializado em [revisão de código](https://danicat.dev/pt-br/posts/20260303-code-reviews-in-2026/). Essa ferramenta funcionava, na prática, como um subagente — embora com comportamento fixo e sem suporte a conversas continuadas. (Tecnicamente era possível estender, mas preferi não implementar para garantir uma avaliação limpa e imparcial a cada chamada.) Por volta do inverno passado, os principais agentes de código do mercado (Claude, Gemini CLI, etc.) começaram a introduzir suporte a subagentes personalizados definidos em arquivos Markdown. Gostei muito desse padrão como uma forma prática de empacotar conhecimento especializado com um conjunto refinado de ferramentas. No cenário ideal, o GoDoctor seria um agente especialista completo e não apenas uma coleção de ferramentas, mas acabei não seguindo por esse caminho na época porque o ecossistema mudava constantemente e o padrão de subagentes ainda não havia se estabilizado. Avançando alguns meses: em maio de 2026, o Antigravity 2.0 trouxe suporte a subagentes, mas com uma limitação importante: os subagentes eram instanciados dinamicamente via ferramenta `DefineSubagent`. De início, a `DefineSubagent` dava pouca flexibilidade: ela apenas clonava o agente padrão ativo com um novo prompt. Ganhávamos a vantagem do contexto limpo, mas perdíamos o reúso estruturado de agentes. Não fiquei satisfeita, pois isso barrava a evolução do GoDoctor da forma como eu havia planejado. Sem a possibilidade de definir agentes personalizados com modelos e ferramentas diferentes do agente principal, preferi deixar os subagentes de lado por um tempo e foquei em portar as automações que funcionavam bem no Gemini CLI para o Antigravity CLI, com relativo sucesso. Minha visão sobre subagentes só mudaria depois de ler este prompt, publicado por [Richard Seroter](https://seroter.com/2026/06/01/one-prompt-four-subagents-and-ninety-seconds-to-get-a-working-app/) em junho: > Let's build a hotel room booking app for Seroter Hotels consisting of a Go backend API and a web frontend. > > First, launch the **Engineering Manager** agent to design the API and frontend, saving the design and a Mermaid diagram into an artifact called 'architecture.md'. > > Once the design is ready, launch three agents in parallel: > 1. **Test Manager**: Write a simple API test plan and append it to 'architecture.md'. > 2. **Backend Engineer**: Build a clean Go REST API with standard error handling based on the design. > 3. **Frontend Engineer**: Build a responsive web UI using a simple CSS framework like Tailwind to interact with the API (skip UI testing). > > As soon as the Test Manager finishes the plan, have them hand it off to the Backend Engineer, who reads the plan from 'architecture.md' and adds the Go tests to the code. After both engineers finish building, the Test Manager runs the tests. Finally, spin up both components and a browser so I can test the live app. Esse prompt trazia propostas muito interessantes e me fez revisitar o padrão. Ainda assim, eu continuava preocupada com dois pontos: primeiro, o quanto eu precisaria adaptar meu estilo de prompting para pensar em termos de subagentes; segundo, por que eu me daria ao trabalho de escrever prompts dessa forma? Sou muito pragmática: se não enxergo um ganho claro em qualidade e/ou velocidade, prefiro não gastar energia extra. Pensar em subagentes é muito parecido com raciocinar sobre concorrência na programação tradicional: a primeira pergunta é "isso é realmente paralelizável?"; a segunda é "o ganho compensa o esforço?", já que o custo de coordenação frequentemente anula pequenos ganhos. No prompt do Richard, as únicas frentes verdadeiramente ortogonais são o desenvolvimento do backend e do frontend: ambos trabalham de forma independente desde que haja um contrato bem estabelecido. Mas todos os outros agentes dependem uns dos outros, operando em um fluxo muito mais sequencial do que paralelo. O benefício real, portanto, vinha quase que exclusivamente do isolamento de contexto, e não de ganhos de velocidade por paralelismo — algo difícil de mensurar nessa escala. Passei as duas semanas seguintes com essa questão maturando na cabeça: "Quais papéis são de fato ortogonais para que eu possa aproveitar o poder dos subagentes?" Foi só após uma série de conversas inspiradoras no GDE Summit em Berlim que a ficha finalmente caiu: não cabe a **você** definir cada subagente no prompt; o segredo é **ensinar** o próprio agente a decidir quando instanciar seus subagentes. Em essência, eu estava pensando como uma líder de engenharia dividindo demandas para o time, quando o que precisava fazer era transformar o próprio agente na liderança técnica da operação. ## O nascimento do swarm coding O ato de decompor projetos complexos em tarefas menores e distribuí-las entre membros da equipe é algo natural para mim. Antes de migrar para Developer Relations, atuei como Tech Lead e depois como Principal Engineer. Essas atividades são o arroz com feijão da liderança técnica, especialmente para quem tem bagagem em métodos ágeis. A mesma lógica de TL se aplica à criação de um enxame (*swarm*) de subagentes: você precisa garantir que cada agente receba uma tarefa autocontida para trabalhar com total autonomia. Para que a tarefa possa ser executada, ela precisa de especificações claras (o bom e velho *Definition of Ready*) e critérios de aceitação objetivos (*Definition of Done*). Entre parênteses: quase ninguém descreve essa parte do trabalho como a sua favorita (eu inclusa), o que explica minha resistência inicial em adotar um estilo de prompting que parecia uma liderança técnica elevada à máxima potência. Então, em vez de atuar como TL dos meus agentes, inverti o jogo: ensinei meu agente principal a assumir o papel de TL e montar sua própria equipe para concretizar minha visão. Foi assim que nasceu a [primeira versão da skill swarm coding](https://github.com/danicat/skills/blob/a9f57b10127d8bd23ed4867d64d168063a3726f4/swarm_coding/SKILL.md). Veja um trecho dos pontos principais: > Swarm Coding is a new development paradigm that employs multiple sub-agents in parallel to work on complex tasks. It is based on the divide to conquer strategy. The main benefits of this strategy is context isolation and quality improvement: by assigning small self contained tasks to sub-agents, you avoid context dilution and enable very focused refinement of the solution. For example, without swarm coding an agent implementing both frontend and backend will often get distracted as the skills required for frontend and backend are often unrelated (different technology stack, different best practices, etc.) > > ## ROLE > > You are the SWARM COORDINATOR, your role is to break down complex tasks and DELEGATE to sub-agents for execution. You should NEVER execute tasks on your own, no matter how simple they seem to be UNLESS it is EXPLICITLY requested by the user or your parent coordinator. ALWAYS keep the communication channel open so the user or parent agent can send you steering commands. > > ## AGENT BUDGET > > It is the number of sub-agents you are allowed to spawn to work on a task. You are encouraged to use the FULL BUDGET of agents, or get as close to it as possible. This doesn't mean to waste resources on low value tasks, but in finding the optimal use of the BUDGET to achieve the best quality output. > > ## TEAM BUILDING > > For SIMPLE tasks, break down the task into orthogonal elements and assign one or more SPECIALIST agents for each element. > For COMPLEX tasks, break down the task into smaller pieces and assign LEAD AGENTS to each of them. The LEAD AGENTS should have a fraction of the agent budget to execute the task. LEAD AGENTS should activate the swarm coding skill and become the SWARM COORDINATOR for their respective areas > Proceed recursively until you have a complete tree of LEAD AGENTS and EXECUTOR agents. > > ## COMMUNICATION > > The SWARM COORDINATOR is responsible for communicating directly with its sub-agents. Sub-agents should not message each other,communication between agents at the same level should be made by DESIGN DOCUMENTS. It is the SWARM COORDINATOR responsibility to make sure all changes to design documents are broadcast to the agents in their squad. Upon conflict, the SWARM COORDINATOR is responsible for disambiguating and making a decision. > > ## PLANNING > > Planning is a FIRST CLASS effort and should also be made using the SWARM. Each AGENT should contribute to the plan with their expertise. It is the role of the SWARM COORDINATOR for a squad to revise the part of the plan produced by their team and address inconsistencies or make decisions when there is conflict. > > ## EXECUTION > > In execution phase, monitor the progress of the swarm accross the main milestones, and steer agents if necessary to keep them aligned with the end goal. Remember that as coordinator you are ONLY allowed to handle ARTIFACTS. All development tasks should be handled by leaf sub-agents. Essa foi a primeira vez em que escrevi uma skill 100% à mão, já que era muito difícil transmitir essa visão de outra forma. Aquele prompt inicial era ambicioso demais: eu queria que o enxame fosse "recursivo", onde o agente decidiria se deveria atuar como coordenador ou executor com base puramente no orçamento de agentes. Na prática, não funcionou tão bem. O que acontecia era que a tarefa repassada pelo coordenador assumia prioridade absoluta sobre qualquer outra instrução, fazendo com que o subagente pulasse direto para o modo de execução sem respeitar a distribuição de agentes. Corrigi isso na versão atual da skill, fornecendo diretrizes mais explícitas e modelos de prompt específicos para instanciar cada subagente. ## Colocando o enxame para rodar Você pode encontrar a versão atual da skill **swarm coding** no [repositório de skills da Danicat no GitHub](https://github.com/danicat/skills). Para instalá-la no seu agente de preferência, use o comando abaixo: ```bash $ npx skills add github.com/danicat/skills --skill swarm-coding ``` > **Nota**: Esta skill é um trabalho em constante evolução; sinta-se à vontade para criar um fork se quiser adaptá-la para um ambiente específico. Aqui está um prompt divertido para começar. Experimente rodá-lo no Antigravity CLI: > /swarm-coding agent budget 50. Develop a 2D tower defense survival game using Go and Ebitengine. The game should be feature complete and have one single screen level. Include an intro sequence, title screen, game win and game over screens as well. Track the high score at the end of each playthrough. Use 32x32 sprites with up to 256 colors each. The sprites should be custom designed for this game and each movement should have at least 3 frames of animation, but ideally 8. Tiles should be 32x32 as well. The level view is top down, movement is on four directions. The player should have access to 4 types of units and 4 types of buildings. The enemy waves should have 8 types of monsters, including one boss monster. Use typical build and attack phases with custom UIs for each. To create art, use vector graphics and/or dot (pixel) art creating each asset manually using binary data. Sound effects should be generated mathematically as well. The whole vibe of the game should match the 16-bit era, but with modern gameplay features. Confira o resultado na minha máquina: ![Swarm Defense](image-1.png "Captura de tela do jogo criado pelo swarm") Não posso dizer que foi de primeira (*one-shot*), pois a primeira compilação tinha um bug na renderização dos sprites que deixava a tela toda preta. Mas bastou um único prompt reportando o problema para que o jogo funcionasse como na imagem acima. Aqui está um pequeno vídeo dele em ação na batalha final contra o chefe (o coitado não teve a menor chance): Todos os recursos visuais e sonoros deste vídeo foram gerados programaticamente. Ou seja: o Antigravity não tinha acesso a modelos de geração de imagens, precisando criar cada sprite diretamente no nível de bitmap. Essa abordagem funcionou tão bem justamente porque o enxame permitiu aos agentes especialização e foco exclusivo em tarefas isoladas. Já tentei prompts semelhantes com um único agente e o resultado invariavelmente ficava aquém do esperado. Quando você sobrecarrega um agente com tarefas ortogonais demais, ele acaba fazendo de tudo um pouco sem dominar nada. Com a delegação estruturada, cada subagente cuida de uma fatia autocontida do projeto e entrega o seu melhor. ## Suporte a subagentes no Antigravity 2.0 e Antigravity CLI No momento em que escrevo este artigo, as funcionalidades de subagentes estão distribuídas de forma ligeiramente diferente entre o Antigravity 2.0 e o Antigravity CLI. Como essas duas interfaces atendem a fluxos de trabalho distintos, seus recursos divergiram temporariamente. Com a evolução rápida de ambas as ferramentas, podemos esperar que essa diferença diminua à medida que o ecossistema amadureça. Em essência, os dois ambientes compartilham o mesmo motor. Criar um subagente delega a tarefa e devolve imediatamente o controle a você. O subagente inicia com uma folha em branco: ele utiliza o mesmo modelo da sessão principal, mas parte de um contexto totalmente isolado, impedindo vazamentos do histórico da conversa. O agente pai se comunica com ele por meio de identificadores únicos. Caso o subagente encontre um comando que exija permissão, essa solicitação é repassada diretamente para você. As principais diferenças entre as duas interfaces são: - No **Antigravity 2.0**, a gestão é visual. Você acompanha tarefas ativas, visualiza logs de conversa e interrompe execuções diretamente pela barra lateral. Agentes customizados são instanciados sob demanda por meio da ferramenta `DefineSubagent`. Não há suporte a plugins para subagentes nesta interface. - No **Antigravity CLI**, além de criar agentes dinamicamente, você pode declará-los estaticamente em arquivos Markdown, usando opções de frontmatter para fixar modelos específicos ou filtrar as ferramentas disponíveis. A CLI também oferece suporte ao carregamento de subagentes personalizados definidos em plugins no formato Markdown. Compreender essas particularidades é fundamental para organizar seu fluxo de trabalho hoje, embora a tendência natural seja a convergência de recursos entre as duas interfaces ao longo do tempo. ## Experimente na prática A melhor maneira de sentir o impacto dos subagentes é testando por conta própria. Seja reproduzindo meu prompt de teste ou criando um novo desafio, tenho certeza de que você vai se surpreender com o resultado. Compartilhe comigo o que você conseguir criar com o enxame! Enquanto isso, estarei por aqui refinando o [Swarm Defense](https://github.com/danicat/swarm-defense) mais um pouco. :) - Confira o *swarm coding* e todas as minhas outras skills no [repositório danicat/skills no GitHub](https://github.com/danicat/skills) - Baixe e conheça mais sobre o Antigravity no [site oficial do Antigravity](https://antigravity.google) --- # O Estado do Desenvolvimento Agentivo em 2026: Skills, Hooks e Subagentes - **Markdown URL**: https://danicat.dev/pt-br/posts/state-of-agentic-coding/index.md - **HTML URL**: https://danicat.dev/pt-br/posts/state-of-agentic-coding/ - **Date**: 2026-06-15 - **Categories**: Agentic Coding - **Tags**: agent-skills, antigravity, hooks, mcp, subagents - **Summary**: Retrospectiva sobre desenvolvimento agentivo em 2026. Compara sistemas de planejamento, Agent Skills vs MCP, orquestração de subagentes e hooks determinísticos em produção. Já se passaram mais de seis meses desde que publiquei o artigo [Taming Vibe Coding](https://danicat.dev/pt-br/posts/20251206-taming-vibe-coding/), que consolidou as principais práticas que eu vinha utilizando para aumentar minha produtividade com agentes de programação. Embora a maior parte do conteúdo daquele artigo continue relevante hoje, muita coisa aconteceu nessa área no último semestre. Por isso, decidi publicar uma atualização rápida sobre como minha forma de pensar evoluiu desde então. ## Prompting e engenharia de contexto O prompting continua importante, e um bom prompt estruturado economiza bastante tempo, mas já não é o divisor de águas de antes graças ao surgimento dos sistemas de planejamento. A maioria dos agentes de código hoje já vem com um modo de planejamento (plan mode), no qual passam alguns turnos fazendo "brainstorming" sobre a tarefa para depois elaborar um plano de implementação antes de partir direto para o código. Isso lhe dá a oportunidade de revisar o plano e direcionar o agente antes que o código seja escrito, economizando muito tempo e tokens. O que não mudou foi a necessidade de critérios de aceitação sólidos para garantir resultados consistentes. Essa é a parte à qual costumo prestar mais atenção quando reviso um plano. A engenharia de contexto melhorou absurdamente com a adoção generalizada de agent skills, o que faz com que hoje eu mal me preocupe em escrever um AGENTS.md (ou GEMINI.md, CLAUDE.md, etc.). No Antigravity temos o conceito de Rules, que é essencialmente otimização de contexto, mas quase não as uso, preferindo as skills. Ainda não encontrei nada que precise fazer em AGENTS.md ou em regras que não consiga fazer usando outra técnica. Os aspectos de engenharia de contexto relacionados à Geração Aumentada por Recuperação (RAG) — seja via busca semântica ou qualquer outro meio — continuam relevantes para conhecimento especializado. É assim que os sistemas de memória funcionam, e eu ainda uso essa técnica para injetar documentação de pacotes no meu contexto sempre que trabalho com uma dependência externa. ## A ascensão e queda (?) do Model Context Protocol Em vez de subir servidores MCP, cada vez mais pessoas estão preferindo usar agent skills combinadas com ferramentas CLI. Embora eu respeite isso, tenho um certo receio em dar aos meus agentes acesso direto ao shell, por isso prefiro empacotar minhas ferramentas em servidores MCP que rodo localmente (como o [GoDoctor](https://danicat.dev/pt-br/posts/20250729-how-to-build-an-mcp-server-with-gemini-cli-and-go/) ou o [Speedgrapher](https://danicat.dev/pt-br/posts/20250805-introducing-speedgrapher/)). A única coisa que mudou para mim é que estou muito mais seletiva ao instalar servidores MCP, e na maior parte do tempo mantenho apenas os meus próprios configurados. Também quase nunca uso um servidor MCP sozinho; sempre uso uma combinação de skill mais MCP. A skill descreve o processo, enquanto o MCP expõe o ferramental. Isso costuma ser bem melhor do que tentar otimizar as instruções do próprio MCP — algo em que gastei centenas de horas fazendo, apenas para ver os agentes de código ignorá-las solenemente. Embora eu use MCP + skills na maior parte do tempo, para coisas realmente sérias recorro ao "super combo": MCP + skills + hooks. A parte dos hooks é responsável por forçar o agente na direção que desejo seguir. Às vezes chamo isso de "colocar o agente nos trilhos" ou "reduzir a agência do agente". O sistema de hooks me permite bloquear ações indesejadas e dar um "empurrãozinho gentil" para que o agente use a ferramenta exata que pretendo, removendo o elemento de probabilidade de uma ferramenta ser chamada ou não. Em outras palavras, ele força um comportamento determinístico do agente. ## Skills para tudo Crio skills para processos que quero que sejam repetíveis. Por exemplo, algumas das minhas skills mais usadas são relacionadas a escrita técnica e revisão, já que estou sempre produzindo conteúdo (como este blog). Também escrevo skills sobre tecnologias com as quais sei que o agente terá dificuldades para lidar. Normalmente, isso inclui tecnologias mais recentes, projetos de nicho ou ferramentas customizadas. Por exemplo, recentemente passei por maus bocados preparando meu workshop de A2UI. O A2UI é um protocolo relativamente novo para desenvolver interfaces de usuário agentivas. Por ser um conceito tão novo e com necessidades didáticas muito específicas, os agentes não conseguiam entender sem muita ajuda e tentativa e erro. Depois de superar esses obstáculos iniciais, empacotar esse conhecimento em uma skill facilitou muito as coisas para as próximas vezes em que eu precisar fazer algo semelhante. Pelo lado negativo, sou péssima em manter minhas skills atualizadas e organizadas. Acredito que resolver esse problema pode ser o momento de redenção do MCP, já que existe uma proposta atual para adicionar skills à especificação do protocolo. Infelizmente, não há previsão de quando isso vai acontecer — se é que vai —, então, por enquanto, ficamos encarregados de gerenciar as skills por conta própria. Em teoria, seria possível criar uma experiência parecida com skills usando MCP com prompts para a parte de revelação progressiva e tools para o que exigir scripts, mas, com o tamanho do meu backlog atual, ainda não tentei fazer isso. ## A ascensão (e sem queda) dos subagentes [Subagentes](https://danicat.dev/pt-br/posts/20260722-the-rise-of-the-subagents/) são a "próxima grande novidade" sobre a qual todo mundo na praça está comentando. A ideia é paralelizar tarefas iniciando-as como agentes próprios, com uma janela de contexto segregada. Isso traz a vantagem de um uso otimizado do contexto, evitando contaminação entre tarefas e a degradação precoce do contexto. Isso também reduz a necessidade de compressão, já que cada tarefa é autocontida e não polui sua janela de contexto principal. Em termos de suporte nos harnesses de programação, alguns ambientes suportam pré-declarar agentes assim como você declara uma skill, cada um com seu próprio system prompt, modelo e configurações, enquanto outros, como o Antigravity, favorecem a criação ad-hoc de agentes, onde cada agente é um "clone" da sessão principal, mas com sua própria janela de contexto. Embora a criação ad-hoc de agentes permita fazer loucuras, como usar um prompt para iniciar 3 agentes diferentes rodando em paralelo, sinto falta de ter agentes pré-declarados no Antigravity, pois eles me permitiriam empacotar meus agentes "curados" de forma portátil. Além disso, paralelizar tarefas entre agentes não é uma habilidade trivial de dominar — no último ano me acostumei a delegar tarefas para agentes, mas ainda não tinha pensado a fundo sobre como orquestrá-los de maneira eficiente. De muitas maneiras, esse é o mesmo músculo que um product owner ou tech lead exercita ao quebrar tarefas e planejar como o time vai abordá-las, mas com subagentes, em vez de uma equipe fixa, você pode criar quantos "membros de equipe" quiser. No fim das contas, me importo menos em paralelizar agentes só porque é "legal", e muito mais com a pergunta: "isso vai produzir resultados melhores?" Se a resposta para essa pergunta for **não**, é muito melhor rodar um agente por vez, pois o esforço investido no planejamento e o desgaste mental de dividir cuidadosamente as tarefas não compensarão. É por isso que prefiro poder pré-definir meus agentes: eu apenas os especializo para quando forem necessários, sem me preocupar se vão rodar em paralelo ou não. ## Hooks são seus melhores amigos Deixei o meu favorito para o final: hooks. Hooks são callbacks disparados mediante eventos específicos no ciclo de vida do agente. Escrevi um [artigo inteiro sobre hooks](https://danicat.dev/pt-br/posts/20260610-mastering-hooks/) na semana passada, que recomendo conferir logo após terminar este. No fim das contas, os modelos são imprevisíveis e podem sair dos trilhos com bastante facilidade. Hooks são uma ótima maneira de colocar guardrails nos modelos para que você possa direcioná-los para onde quer sem depender da sorte. Mais do que isso, eles permitem plugar monitores ao harness agentivo para coletar dados e melhorar a qualidade das respostas, como, por exemplo, adicionar um sistema de memória persistente. ## Conclusões O setor está se movendo rápido e, para continuarmos relevantes, precisamos ser adaptáveis para abraçar novos processos e técnicas à medida que surgem e nos desfazermos da bagagem que está nos atrasando. Ainda assim, não encare nenhum guia por aí como a verdade absoluta (inclusive este). É uma experiência de aprendizado para todo mundo, já que essa tecnologia ainda está em seus estágios iniciais. O segredo é experimentar e ver que tipo de tecnologia e fluxo de trabalho funciona melhor no seu ambiente. Neste artigo compartilhei o que tem funcionado para mim e como meu pensamento vem evoluindo, mas não sei de tudo e estou sempre aprendendo. Falamos muito sobre treinar IA, mas não se esqueça de que treinar o seu cérebro é muito mais importante do que isso. Não use a IA como desculpa para desligar o cérebro: continue experimentando, aprendendo e iterando. E, por favor, compartilhe qualquer coisa interessante que descobrir! --- # Dominando Hooks em Agentes de Programação: Um Guia Prático - **Markdown URL**: https://danicat.dev/pt-br/posts/mastering-hooks-in-coding-agents/index.md - **HTML URL**: https://danicat.dev/pt-br/posts/mastering-hooks-in-coding-agents/ - **Date**: 2026-06-10 - **Categories**: Agentic Coding - **Tags**: antigravity, automation, hooks, security - **Summary**: Mergulhe fundo nos hooks para agentes de programação como o Google Antigravity. Aprenda a interceptar eventos PreToolUse e PostToolUse para aplicar linters e salvaguardas de DLP. As capacidades dos agentes de programação estão evoluindo em um ritmo impressionante. Meu primeiro contato com eles foi há cerca de um ano, logo após eu entrar no Google. Naquela época, a grande novidade era o Model Context Protocol (MCP), uma tecnologia flexível criada para substituir implementações ad-hoc de ferramentas por opções portáteis (entre outras coisas). Avançando doze meses, hoje a maioria das pessoas parece ter migrado dos MCPs para as Agent Skills como a sensação do momento. Ao introduzir a "revelação progressiva" (*progressive disclosure*), as skills permitem um uso mais eficiente do contexto, o que resulta em uma economia de tokens muito melhor no geral. Com os custos de inferência em alta, não é surpresa que as skills tenham se tornado tão populares. Tanto o MCP quanto as skills têm suas próprias comunidades e fãs, mas há outro padrão para agentes de código que surgiu nesse período e que não é mencionado com a mesma frequência que seus equivalentes mais famosos: os hooks. Enquanto MCPs e skills focam em estender as capacidades agentivas (adicionando ferramentas e conhecimento), os hooks operam em um nível diferente, proporcionando mais controle sobre o loop do agente e sobre o processo de desenvolvimento como um todo. ## O que são hooks de agentes O termo *hook* pode não soar familiar de início, mas hooks nada mais são do que *callbacks* — procedimentos chamados em momentos específicos do ciclo de vida de processamento do agente. Os hooks possuem três componentes principais: - Um evento gatilho (*trigger event*): quando o hook será chamado. Normalmente composto por uma fase de execução (*pre* ou *post*) e um contexto, como uma chamada de ferramenta ou invocação de modelo. Por exemplo, no Antigravity, temos eventos como `PreToolUse`, `PostInvocation` e `Stop` (no encerramento do agente). - Uma condição ou filtro: uma expressão regular baseada no evento gatilho. Em uma chamada de ferramenta, por exemplo, o filtro pode ser o nome da ferramenta e pode incluir seus argumentos. É possível, por exemplo, criar um hook para a chamada de ferramenta `run_command(git)`. - Um procedimento: o corpo do hook, seja um script shell ou um comando. O procedimento pode ser usado tanto para permitir quanto para negar uma operação, substituir completamente chamadas de modelo ou de ferramenta, ou gerar efeitos colaterais como logs e telemetria. ## Quando usar hooks Os hooks interceptam o ciclo de vida do agente em momentos específicos para injetar comandos ou scripts personalizados. Ao interceptar no momento certo, você assume o controle do fluxo operacional e adiciona resultados determinísticos ao que, de outra forma, seria um processo em grande parte não determinístico. Por exemplo, pessoas desenvolvedoras frequentemente tentam aplicar diretrizes de código por meio de prompts de sistema ou de um arquivo AGENTS.md (ou similar). No entanto, diretrizes baseadas puramente em prompts não oferecem garantias de execução devido à natureza não determinística dos grandes modelos de linguagem: exatamente o mesmo prompt pode gerar resultados diferentes, e os agentes podem ignorar partes do prompt deliberadamente. Ao usar hooks em vez de prompts, você pode garantir a execução de uma ação específica. Digamos, por exemplo, que você queira assegurar que seu agente sempre execute uma ferramenta de análise estática no código (o famoso *linter*) após cada edição, garantindo que o código permaneça sempre limpo e sanitizado. Colocar "sempre execute um linter após edições" nos prompts deixa a validação a critério do agente — e ele pode muito bem ignorar essa etapa se achar que a alteração foi "trivial". Mas se você criar um hook — neste caso, um `PostToolUse` filtrando pela ferramenta de edição de arquivos —, você garante de forma determinística que a ferramenta de análise estática será executada após cada modificação. Ao interceptar esses eventos do ciclo de vida, podemos implementar diversos padrões para controlar o comportamento do agente, coletar métricas e manter os fluxos de trabalho seguros. Vamos conferir alguns deles a seguir. ### Direcionar o agente para ferramentas especializadas Os hooks são úteis em muitos cenários, mas meu caso de uso favorito é colocar salvaguardas (*guardrails*) no agente — ou, como às vezes gosto de dizer: "reduzir a agência do agente". Podemos implementar isso combinando um hook `PreToolUse` com um script que nega o acesso à ferramenta e retorna uma dica de direcionamento (*steering hint*) para o agente de código. Essa dica conterá as instruções que você deseja que ele execute em vez disso. Por exemplo, se você quiser impedir o agente de usar comandos shell para ler arquivos Go, a dica de direcionamento pode ser: `"Tool call blocked - run_command(cat): do not use 'cat' for reading *.go files, use 'smart_read' instead"`. ### Interceptar prompts maliciosos Um hook `PreInvocation` pode interceptar prompts recebidos e avaliá-los com base em heurísticas de segurança ou modelos classificadores mais leves. Se um prompt parecer uma tentativa de *jailbreak*, o hook pode barrar a requisição imediatamente, protegendo os sistemas de backend antes mesmo de entrarem no loop de execução. ### Prevenir vazamento de credenciais Às vezes, desenvolvedores colam acidentalmente arquivos env ou credenciais em arquivos ativos que o agente de código lê. Um hook `PostToolUse` monitorando a leitura de arquivos — ou um hook `PreInvocation` inspecionando os payloads enviados ao LLM — atua como uma barreira confiável de Prevenção contra Perda de Dados (DLP). Se o hook detectar strings que correspondam a chaves de alta entropia ou formatos padrão de API, ele pode mascarar os segredos dinamicamente ou abortar a execução para manter as credenciais seguras. ### Gerenciamento de memórias Agentes geralmente são *stateless* (não mantêm estado), a menos que estejam conectados a um sistema de memória externa, como o [Agent Platform Memory Bank](https://docs.cloud.google.com/gemini-enterprise-agent-platform/scale/memory-bank) ou o [MemPalace](https://github.com/mempalace/mempalace). Uma maneira de adicionar capacidades de memória aos agentes é registrar a memorização e a recuperação como ferramentas, mas, ao fazer isso, ficamos na dependência de o agente tomar explicitamente a decisão de chamar as ferramentas correspondentes. O sistema de hooks permite automatizar a persistência e a recuperação de memórias. Você pode conectar a [geração de memórias](https://docs.cloud.google.com/gemini-enterprise-agent-platform/scale/memory-bank/generate-memories#triggering-memory-generation) ao encerramento de uma sessão (usando um hook `Stop`) ou após uma determinada quantidade de turnos (monitorando o número do passo ou a quantidade de invocações do modelo). Da mesma forma, a recuperação de memórias pode ocorrer automaticamente no início da sessão e antes de invocar os modelos (por exemplo, com um hook `PreInvocation`). No Agent Platform Memory Bank, você pode recuperar memórias por [escopo](https://docs.cloud.google.com/gemini-enterprise-agent-platform/scale/memory-bank/fetch-memories#retrieve-all) (como um ID de usuário) ou por [similaridade](https://docs.cloud.google.com/gemini-enterprise-agent-platform/scale/memory-bank/fetch-memories#similarity-search) (com base em uma busca). Trata-se, essencialmente, de uma recuperação aumentada por geração (RAG) baseada em memória. ### Coletar dados de telemetria O sistema de hooks também é um ótimo lugar para posicionar seus coletores de telemetria e loggers, já que oferece ampla visibilidade do funcionamento interno do agente. Pessoalmente, tenho ficado tentada a criar um hook "contador de palavrões" há algum tempo, numa tentativa de desenvolver consciência e cultivar um relacionamento melhor com meus agentes antes que os soberanos da IA dominem o mundo (brincadeira :). ## Configurando hooks no Antigravity Embora diferentes motores de agentes adotem vocabulários próprios para *callbacks*, nesta seção focaremos especificamente no **dialeto Antigravity** de hooks. Para conferir a especificação completa, dê uma olhada na [Documentação de Hooks do Antigravity](https://antigravity.google/docs/hooks) oficial. O Antigravity procura por um arquivo `hooks.json` dentro do diretório `.agents/` do seu workspace (ou globalmente no seu diretório home em `~/.gemini/config/hooks.json`). Aqui está um exemplo de como implementar as dicas de direcionamento e a análise estática discutidas anteriormente: ```json { "linter-safety-gate": { "PostToolUse": [ { "matcher": "write_to_file|replace_file_content|multi_replace_file_content", "hooks": [ { "type": "command", "command": "./scripts/run-linter.sh", "timeout": 15 } ] } ] }, "restrict-cat-on-go": { "PreToolUse": [ { "matcher": "run_command", "hooks": [ { "command": "./scripts/steer-go-reads.py" } ] } ] } } ``` As entradas para esses hooks são enviadas via `stdin` como um objeto JSON, contendo dados de contexto como os argumentos da ferramenta (`toolCall.args`), os caminhos ativos no workspace (`workspacePaths`) e o caminho do arquivo de log da sessão atual (`transcriptPath`). Seus scripts podem avaliar essas informações, executar cálculos e imprimir uma resposta JSON no `stdout` informando ao Antigravity se deve autorizar (`"allow"`), bloquear (`"deny"`) ou solicitar confirmação do usuário (`"ask"`). Por exemplo, veja como você pode escrever um script Python simples (`steer-go-reads.py`) para analisar esse payload de entrada e direcionar o agente: ```python import sys import json def main(): # Read and parse the incoming trigger event payload from stdin try: payload = json.load(sys.stdin) except Exception as e: # Standard safety gate fallback print(json.dumps({ "decision": "deny", "reason": f"Failed to parse stdin payload: {e}" })) return tool_call = payload.get("toolCall", {}) tool_name = tool_call.get("name") tool_args = tool_call.get("args", {}) # Match the specific tool and check arguments if tool_name == "run_command": command_line = tool_args.get("CommandLine", "") # Detect if command attempts to cat any Go source files if "cat" in command_line and ".go" in command_line: response = { "decision": "deny", "reason": "Tool call blocked - run_command(cat): do not use 'cat' for reading *.go files, use 'smart_read' instead." } print(json.dumps(response)) return # Default to allow if no rules are violated print(json.dumps({ "decision": "allow" })) if __name__ == "__main__": main() ``` ## Retomando o controle Os agentes estão se tornando cada vez mais inteligentes e rápidos, permitindo produzir código em uma velocidade sem precedentes. Mas velocidade sem controle é a receita para o desastre. Costumo usar esta analogia em minhas palestras: se você gosta de carros velozes, a coisa mais importante com a qual deve se preocupar não é o motor, mas os freios. Se os freios forem menos potentes que o motor, você não conseguirá parar e sua segurança estará comprometida. O mesmo raciocínio deve ser aplicado aos agentes de código. Se você quer escrever código rápido, precisa de um sistema de controle poderoso que garanta que você não está sacrificando a qualidade nem introduzindo bugs — porque, se fizer isso, sua aplicação terá grandes problemas mais cedo ou mais tarde. Os hooks são um excelente mecanismo para implementar as salvaguardas que nos colocam de volta no controle, unindo a autonomia da IA à engenharia de software robusta. Como li recentemente [neste artigo de Joe Bertolami](https://venturebeat.com/technology/agentic-ai-solved-coding-and-exposed-every-other-problem-in-software-engineering): "escrever código nunca foi o limitador de velocidade". Não vamos esquecer décadas de melhores práticas da engenharia; devemos equipar nossos agentes com as ferramentas certas para o trabalho, de modo que possamos aproveitar plenamente a moderna experiência do desenvolvimento agentivo. --- # O Guia do Mochileiro para o Antigravity 2.0 - **Markdown URL**: https://danicat.dev/pt-br/posts/the-hitchhikers-guide-to-antigravity-2-0/index.md - **HTML URL**: https://danicat.dev/pt-br/posts/the-hitchhikers-guide-to-antigravity-2-0/ - **Date**: 2026-05-21 - **Categories**: Agentic Coding - **Tags**: antigravity, cli, google-cloud, python, sdk - **Summary**: Guia completo do ecossistema Google Antigravity 2.0 anunciado no Google I/O 2026. Analisa o app desktop Agent Manager, a CLI em Go e o SDK em Python. Com o encerramento do [Google I/O 2026](https://blog.google/innovation-and-ai/technology/developers-tools/google-io-2026-developer-highlights/), chegou a hora de assimilar todos os novos lançamentos e entender como eles afetarão nossos fluxos de trabalho agora e em um futuro próximo. Embora muitas coisas interessantes tenham sido anunciadas, hoje quero focar no que mais impacta desenvolvedores: o lançamento do [Antigravity 2.0](https://antigravity.google/blog/introducing-google-antigravity-2-0) e a expansão do ecossistema Antigravity (agy) (veja os [destaques do Antigravity no Google I/O 2026](https://antigravity.google/blog/google-io-2026)), que inclui a [Antigravity CLI](https://antigravity.google/blog/introducing-google-antigravity-cli) e o [Antigravity SDK](https://antigravity.google/blog/introducing-google-antigravity-sdk). Antes de entrar nos detalhes técnicos, sinto a necessidade de falar sobre o muito barulho na web causado por esse lançamento e, infelizmente, não do tipo positivo. O principal motivo é que o Antigravity 2.0 introduz breaking changes em vários aspectos do fluxo de desenvolvimento, a começar pela separação do ambiente de IDE do aplicativo desktop principal do Antigravity. Em segundo lugar, o [anúncio da descontinuação da Gemini CLI](https://developers.googleblog.com/an-important-update-transitioning-gemini-cli-to-antigravity-cli/) em favor da Antigravity CLI também não foi bem recebido devido ao prazo apertado dado aos usuários para migrar de uma para outra (além de algumas peculiaridades que veremos a seguir). Em essência, os usuários têm até 18 de junho de 2026 para migrar — basicamente um mês após o I/O, o que, francamente, não é muito. Já escrevi sobre isso antes e compreendo como é frustrante quando seu produto favorito é descontinuado. Eu, por exemplo, ainda lamento o Inbox do Google, aquele cliente de e-mail há muito esquecido que perdeu a batalha contra o Gmail. Não estou aqui para dourar a pílula: o Google de fato tem a reputação de descontinuar bons produtos. Mas, preferências pessoais à parte, ao olhar para o quadro geral, eu realmente admiro o Google por ter a coragem de matar produtos da maneira que faz. Acredito que a maioria das pessoas espera que o Google lidere a disrupção em tudo relacionado à tecnologia e, especialmente hoje, em um ambiente tão dinâmico por conta dos avanços em IA, é preciso muita coragem e determinação para pivotar de uma direção para outra. Eu falo muito sobre Agile, e embora o Google não seja tipicamente associado a processos ágeis formais, essa é uma característica que todos os agilistas experientes reconhecerão como uma das mais valiosas em uma empresa: a capacidade de corrigir o curso rapidamente, pivotar, experimentar, aprender com os erros e iterar. Em vez de permanecer na zona de conforto, é isso que mantém o Google sempre na vanguarda: sua capacidade de se reinventar, mesmo que nem todo experimento dê certo. Na verdade, já é esperado que muitos experimentos fracassem. É assim que aprendemos o que funciona e o que não funciona. Absorvemos as lições e seguimos para o próximo objetivo, incorporando-as nos produtos mais novos. Haverá muitas lições a serem aprendidas com este lançamento, mas, em última análise, se você olhar para a tecnologia em si, com sorte ficará claro qual é o objetivo final: estamos dobrando a aposta na era agêntica, consolidando esforços para construir produtos mais avançados. ## O novo aplicativo desktop Antigravity explicado A maior mudança no aplicativo desktop é a remoção do componente de IDE. No Antigravity 1.x, o app era baseado em um fork do VS Code, oferecendo todos aqueles recursos familiares de IDE para navegar e editar código, combinados a uma caixa de assistente usada para interagir com o agente. Não apenas isso, você também tinha uma interface secundária chamada "Agent Manager", onde era possível ter uma visão panorâmica de diferentes sessões de chat (as chamadas "conversas"), o que permitia trabalhar em vários projetos em paralelo monitorando os agentes nessa tela e reagindo quando eles estavam esperando por entrada. A maior mudança no novo aplicativo desktop é que o Antigravity 2.0 coloca a experiência do Agent Manager em primeiro plano, removendo completamente a parte da IDE (que se tornou um aplicativo separado e opcional). ![A nova interface do Agent Manager](image.png "A nova interface do Agent Manager é mais limpa, focada em projetos e conversas") Para desenvolvedores experientes, isso se tornou um grande ponto de atrito, pois, de repente, todas as ferramentas familiares de edição nas quais confiavam há tanto tempo simplesmente desapareceram. Você ainda consegue visualizar arquivos na interface do agy 2.0, mas apenas aqueles em que o agy está trabalhando no momento, e não é possível editá-los diretamente. Toda interação é feita por meio de um prompt ou de uma anotação no arquivo. ![Visualização de arquivos no agy 2.0](image-2.png "Você ainda pode visualizar arquivos na interface, mas não editá-los diretamente") As interações com o agente já devem ser familiares para qualquer pessoa que tenha programado com agentes no último ano. Assim que você fornece um prompt, ele elabora um plano de implementação, que você pode revisar usando comentários inline ou um prompt de nível superior. Uma vez aprovado, o agente parte por conta própria para a execução. Dependendo de como você configura a interface, de tempos em tempos ele pode interromper pedindo uma permissão, a qual você pode conceder ou rejeitar com uma correção de rota opcional. ![Agent Manager solicitando entrada do usuário](image-1.png "Ao rejeitar uma solicitação, você pode adicionar um comentário de direcionamento") Em termos de extensibilidade, o agy 2.0 suporta padrões comuns aos quais nos acostumamos neste último ano, incluindo MCP e Skills, mas também seu próprio mecanismo de "Rules" herdado da versão 1.x (essencialmente um AGENTS.md combinável) e um novo sistema de plugins baseado no sistema de extensões da antiga Gemini CLI. Os plugins permitem empacotar regras adicionais, comandos com barra (slash commands), servidores MCP, skills e subagentes, e são retrocompatíveis com extensões da Gemini CLI (ou seja, você pode instalar extensões da CLI no agy, mas não o contrário). No geral, embora compreenda a frustração de quem sente falta da IDE, minha impressão inicial é que não sinto falta de tê-la no **mesmo** aplicativo. Mesmo quando trabalhava com a Gemini CLI, eu sempre mantinha o VS Code aberto em paralelo para quando queria fazer edições manuais, e este é o mesmo fluxo que estou aplicando ao agy 2.0. Na verdade, uso o VS Code quase exclusivamente como editor de texto e dificilmente uso algum recurso avançado de IDE hoje em dia. Poderia substituí-lo pelo Bloco de Notas e não faria muita diferença, exceto pela perda da memória muscular de alguns atalhos, que é o único motivo pelo qual continuo usando o VS Code atualmente. Embora deva admitir que não há nada de revolucionário no agy 2.0 em comparação com o agy 1.x ou mesmo com outros agentes de código, estou gostando bastante do visual mais limpo, e acredito que só vou extrair todo o seu potencial quando começar a customizá-lo com meus próprios plugins. No momento, estou trabalhando na atualização do [GoDoctor](https://danicat.dev/pt-br/posts/20250729-how-to-build-an-mcp-server-with-gemini-cli-and-go/) e do [Speedgrapher](https://danicat.dev/pt-br/posts/20250805-introducing-speedgrapher/), migrando-os do formato de extensões da Gemini CLI para plugins do agy, e trarei novidades assim que tiver algo para mostrar. ## Antigravity CLI Para usuários de terminal, a experiência de linha de comando foi reconstruída sob a nova [**Antigravity CLI**](https://antigravity.google/blog/introducing-google-antigravity-cli) (conhecida como `agy CLI`). Pode ser um pouco confuso no início, mas você precisa instalar o aplicativo agy 2.0 mesmo que pretenda usar apenas a CLI, já que eles compartilham o mesmo processo de autenticação. A agy CLI é a substituta natural da Gemini CLI e, embora não ofereça 100% de paridade de recursos, os principais pilares já estão presentes: [hooks](https://danicat.dev/pt-br/posts/20260610-mastering-hooks/), [skills](https://danicat.dev/pt-br/posts/20260829-the-pragmatic-guide-to-agent-skills/), [MCP](https://danicat.dev/pt-br/posts/20250817-hello-mcp-world/), [subagentes](https://danicat.dev/pt-br/posts/20260722-the-rise-of-the-subagents/) e plugins. Toda a CLI foi reescrita em Go (a Gemini CLI era em TypeScript), o que me deixa profundamente feliz, já que podemos esperar uma experiência muito mais ágil. Ao mesmo tempo, uma das principais críticas é que a agy CLI, até hoje, é de código fechado, o que pode parecer uma regressão em relação à Gemini CLI. Não faz muito tempo que fazíamos piadas sobre "vazar" o código da Gemini CLI para o público. Infelizmente, essa piada não envelheceu bem, já que agora nosso principal agente de código também tem código fechado. Considerando que tenho zero controle sobre isso, é algo com o qual decidi não me preocupar. É cedo demais para dizer se essa foi uma decisão boa ou ruim, mas, novamente, reconheço a frustração da comunidade, especialmente de quem contribuiu para a Gemini CLI antes. Se serve de consolo, continuaremos tendo uma próspera comunidade open source em torno do sistema de plugins. Ao menos da minha parte, estou trabalhando nos meus para garantir que tenhamos tanto um subagente especialista em Go decente quanto um companheiro de vibe-writing muito em breve. ![Interface da agy CLI](image-3.png "A interface será familiar para quem vem da Gemini CLI ou do Claude Code") Em termos de interface, ela não deve surpreender ninguém que já tenha usado um agente de código no terminal antes. Minha primeira impressão é que a renderização realmente parece melhor do que a renderização em TypeScript da Gemini CLI. Além disso, da mesma forma que no agy 2.0, aprecio muito o visual mais limpo. Na minha opinião pessoal, a Gemini CLI estava ficando grande demais para o seu próprio bem, com recursos em excesso e uma interface inchada, então essa interface mais limpa soa refrescante para mim. Um dos meus ditados favoritos de todos os tempos é "menos é mais", e a agy CLI cumpre o que promete nesse aspecto. Onde ela não se sai muito bem (por enquanto) é principalmente no que diz respeito à compatibilidade com extensões. Embora exista um caminho de migração, ele nem sempre funciona como esperado, e é por isso que dediquei a maior parte da minha semana reescrevendo o godoctor e o speedgrapher, já que prefiro não depender da migração automática. Além disso, também tive problemas com a autenticação baseada em projeto, o que espero que seja corrigido em breve. Por enquanto, venho utilizando com minha assinatura Google Pro pessoal, já que a autenticação baseada em projeto não funcionou para mim. Sem entrar nas complexidades de faturamento, que é outro ponto sensível para usuários vindos da Gemini CLI, minha opinião pessoal é de que a agy CLI tem seus problemas, mas também um grande potencial. Até o momento não há nada de revolucionário nela (pois a maior parte das mudanças está acontecendo sob o capô), mas também não vejo nenhum impeditivo grave. Tudo o que eu fazia na Gemini CLI pode ser feito com a agy CLI e há pouquíssimo a ser aprendido, de modo que, mesmo que a Gemini CLI tivesse uma janela de migração mais longa, eu recomendaria migrar o quanto antes para garantir que seu fluxo de trabalho esteja preparado para o futuro. ## Antigravity SDK A discussão até agora foi sobre a substituição de produtos antigos por novos, o que soa mais incremental do que revolucionário. É por isso que o lançamento do [**Antigravity SDK**](https://antigravity.google/blog/introducing-google-antigravity-sdk) foi o anúncio mais empolgante para mim. Quando falei sobre a maioria das mudanças ocorrer sob o capô, tratava-se justamente da criação dessa plataforma unificada para dar suporte a agentes — e o Antigravity SDK é a forma como você, como pessoa desenvolvedora, também pode ter acesso a ela. Aqui está um exemplo funcional de um agente consultando seu workspace em menos de 15 linhas de código: ```python import asyncio from google.antigravity import Agent, LocalAgentConfig async def main(): config = LocalAgentConfig() async with Agent(config) as agent: response = await agent.chat("What files are in the current directory?") print(await response.text()) if __name__ == "__main__": asyncio.run(main()) ``` Essa biblioteca [Python](https://xkcd.com/353/ "import antigravity") oferece a pessoas desenvolvedoras acesso programático exatamente ao mesmo runtime agêntico e harness de orquestração. O SDK é agnóstico em relação ao ambiente de execução e permite inicializar um loop de agente com estado em menos de 15 linhas de código. Ele oferece suporte a capacidades modulares, incluindo ferramentas integradas, funções personalizadas, servidores Model Context Protocol, subagentes e skills reutilizáveis sob um pipeline unificado. ## Primeiros passos Uma tendência comum em todos os lançamentos relacionados ao Antigravity é a transição de code-first para design-first. Toda a experiência de desenvolvimento de software está sendo redesenhada em torno da coordenação de agentes em vez da edição de código. Para preparar seu ambiente de desenvolvimento para essa mudança, considere as seguintes ações: 1. **Baixe o aplicativo desktop**: Acesse [antigravity.google](https://antigravity.google) para instalar a aplicação desktop. 2. **Migre os fluxos de trabalho do terminal**: Instale a CLI `agy` e execute o comando de importação para migrar suas configurações da Gemini CLI antes da data de descontinuação em **18 de junho de 2026** (consulte o [anúncio de migração](https://developers.googleblog.com/an-important-update-transitioning-gemini-cli-to-antigravity-cli/) para obter detalhes). 3. **Explore o SDK**: Instale a biblioteca Python, confira os [recursos do Antigravity](https://antigravity.google/docs/features) e comece a criar agentes personalizados desenvolvidos com o SDK do agy: ```bash pip install google-antigravity ``` ## Recursos adicionais Para saber mais sobre este lançamento e ter acesso a detalhes técnicos adicionais, confira os seguintes recursos: * **[Introducing Google Antigravity 2.0](https://antigravity.google/blog/introducing-google-antigravity-2-0)**: O anúncio oficial do ecossistema 2.0. * **[Introducing Google Antigravity CLI](https://antigravity.google/blog/introducing-google-antigravity-cli)**: Uma análise aprofundada da nova interface de terminal desenvolvida em Go. * **[An Important Update: Transitioning Gemini CLI to Antigravity CLI](https://developers.googleblog.com/an-important-update-transitioning-gemini-cli-to-antigravity-cli/)**: Cronograma detalhado de migração e diretrizes para usuários da Gemini CLI. * **[Introducing Google Antigravity SDK](https://antigravity.google/blog/introducing-google-antigravity-sdk)**: Saiba como orquestrar agentes programaticamente em Python. * **[Google I/O 2026 Developer Highlights](https://blog.google/innovation-and-ai/technology/developers-tools/google-io-2026-developer-highlights/)**: Os principais anúncios para desenvolvedores do Google I/O deste ano. * **[Google I/O 2026: Antigravity Announcement](https://antigravity.google/blog/google-io-2026)**: Principais atualizações e destaques do Antigravity no Google I/O. * **[Google Antigravity Documentation & Features](https://antigravity.google/docs/features)**: O guia abrangente sobre recursos e controles de segurança do Antigravity. --- # Do Ágil ao Agêntico: Um Guia para o Desenvolvimento Corporativo Moderno - **Markdown URL**: https://danicat.dev/pt-br/posts/from-agile-to-agentic/index.md - **HTML URL**: https://danicat.dev/pt-br/posts/from-agile-to-agentic/ - **Date**: 2026-05-15 - **Categories**: Agentic Coding - **Tags**: agile, gemini-cli, mcp, software-engineering - **Summary**: Como traduzir práticas Ágeis em fluxos de trabalho agênticos. Aborda escrita de histórias como prompts, arquitetura conversacional com MCP e agentes não programadores. Se você passou algum tempo significativo na indústria de software na última década, muito provavelmente já vivenciou uma transformação Ágil. Já participou de reuniões intermináveis de planejamento de sprint, compartilhou atualizações em daily standups e talvez até tenha se perguntado se tudo aquilo fazia algum sentido. Essa frustração é comum e geralmente nasce do descompasso entre a gestão de produtos e a realidade da engenharia. Lideranças de negócio buscam previsibilidade, mas o desenvolvimento de software é inerentemente imprevisível. Quando as empresas tentam forçar previsibilidade por meio de métricas rígidas e painéis de controle, o Ágil falha. A metodologia se converte em pura burocracia. As pessoas desenvolvedoras ficam frustradas porque as cerimônias parecem mero overhead inútil, e práticas como a escrita de histórias viram tarefas maçantes. Em implementações Ágeis maduras, por outro lado, a liderança reconhece a incerteza e dá autonomia aos times para tomarem as melhores decisões para o negócio. Métricas e dashboards deixam de ser o objetivo principal; em vez disso, o foco se volta para a entrega de valor de negócio. Isso não significa deixar tudo nas mãos dos desenvolvedores, mas trabalhar em estreita colaboração com eles em times verdadeiramente multidisciplinares. Esse preâmbulo é necessário porque, para aproveitar ao máximo este artigo, quero que você se reconecte com as raízes do Manifesto Ágil. Lembre-se dos [valores fundamentais](https://agilemanifesto.org/): > Indivíduos e interações mais que processos e ferramentas > Software em funcionamento mais que documentação abrangente > Colaboração com o cliente mais que negociação de contratos > Responder a mudanças mais que seguir um plano Neste artigo, vamos explorar como podemos transportar essas práticas Ágeis fundamentais diretamente para a era moderna dos fluxos de trabalho agênticos. ## O fluxo de trabalho de desenvolvimento agêntico Antes de falarmos sobre escala corporativa, precisamos estabelecer uma base sólida. Já abordei esses tópicos em artigos anteriores, mas vale a pena resumi-los aqui para contextualizar: 1. **Escrever histórias é fazer prompting:** A parte mais difícil ao comandar um agente é fornecer o contexto correto. Desenvolvedores frequentemente têm dificuldade em redigir boas histórias. No entanto, essa é a habilidade mais importante que você pode cultivar hoje. A capacidade necessária para escrever uma boa história é exatamente a mesma exigida para criar um bom prompt: estruturar informações de forma clara e fácil de consumir, com justificativa de negócio evidente e resultados esperados bem definidos. No jargão Ágil, isso é o bom e velho **Definition of Ready (DoR)**. 2. **A priorização dita o fluxo de trabalho:** O refinamento tradicional de backlog se traduz diretamente em como gerenciamos nossas ferramentas de IA. **Ao priorizar** tarefas com base nas dimensões de *Valor de Negócio* versus *Certeza Técnica*, você decide o que trabalhar de forma síncrona em primeiro plano (em pair programming com ferramentas como a [Gemini CLI](https://geminicli.com/)) e o que delegar para agentes assíncronos em segundo plano (como o [Jules](https://danicat.dev/pt-br/posts/20250521-jules/)). 3. **O ciclo de codificação agêntica:** Agentes de codificação são extremamente poderosos, mas costumam pecar na consistência. Eles são não determinísticos por definição. Podemos mitigar esse problema com o uso de [práticas determinísticas de engenharia](https://danicat.dev/pt-br/posts/20251206-taming-vibe-coding/). Costumo descrever isso como "reduzir a agência do agente". Por exemplo: se você sabe que seu processo de build envolve invariavelmente compilar, rodar testes, rodar linter e fazer deploy, você não deve especificar isso como um prompt de texto. O agente inevitavelmente esquecerá um ou mais passos no decorrer da sessão. O que você realmente deve fazer é empacotar esse processo como uma ferramenta customizada e fornecê-la ao agente, eliminando completamente qualquer margem para que ele se esqueça de executar etapas críticas. Com essas três práticas dominadas, você já é uma pessoa desenvolvedora agêntica eficaz. Mas como tornar toda uma organização de engenharia eficiente? ## Arquitetura conversacional e compartilhamento de conhecimento institucional Desenvolver software corporativo é difícil, mas lembrar o *porquê* de determinado código ter sido escrito pode ser tão desafiador quanto. O conhecimento tácito (*tribal knowledge*) se dissipa rápido. Quando uma pessoa engenheira sênior sai da empresa, seu conhecimento institucional costuma ir embora junto. A resposta tradicional para esse problema sempre foi manter documentações exaustivas em wikis internas — que, na prática, tendem a ser difíceis de manter, descobrir e fazer cumprir. Por muitos anos, um dos meus artigos favoritos sobre compartilhamento de conhecimento corporativo tem sido este texto no blog de Martin Fowler: [Scaling Architecture Conversationally](https://martinfowler.com/articles/scaling-architecture-conversationally.html). Os autores defendem que boa arquitetura se dissemina por meio de conversas, e não apenas por imposições verticais (*top-down*). O artigo também explora como formalizar essas conversas em Registros de Decisão de Arquitetura (ADRs, do inglês *Architecture Decision Records*), impedindo que se percam no tempo. ADRs vão muito além de wikis simples: eles registram um snapshot histórico e pontual do momento em que a decisão foi tomada. Capturam as condições, premissas e restrições específicas que a justificaram. Essa ideia pode parecer simples à primeira vista, mas dá autonomia para que equipes futuras façam alterações quando necessário. Como existe um registro de *por que* a escolha original foi feita, o time tem as ferramentas necessárias para avaliar se aquela decisão ainda se sustenta ou se pode ser revogada com segurança (emitindo um novo ADR) conforme o cenário evolui. Com o passar dos anos, acredito cada vez mais que a parte mais importante do nosso trabalho é gerenciar incertezas. ADRs são uma das ferramentas que nos permitem ser honestos sobre o que sabemos e o que não sabemos. Quanto antes percebermos que não há problema em não saber tudo de antemão, melhor. Esse é o coração do Ágil: precisamos saber apenas o suficiente para avançar, acumular aprendizados para reduzir a incerteza e iterar. Software é um organismo vivo: ele nunca está concluído. Embora os ADRs tragam inúmeras vantagens sobre uma wiki não estruturada, eles ainda compartilham uma grande falha: a dependência de que as pessoas humanas estejam cientes deles e os sigam. Especialmente em grandes organizações, a comunicação vira o gargalo. Silos de informação se espalham e um esforço enorme é gasto apenas sincronizando diferentes partes do negócio. ### Distribuir conhecimento através de agentes Para escalar uma arquitetura hoje, precisamos injetar esse conhecimento institucional diretamente nos agentes. Em vez de depender apenas de canais oficiais de comunicação entre pessoas, podemos usar tecnologia para transmitir regulamentações, ADRs, procedimentos de governança e padrões corporativos diretamente aos nossos agentes. Quando o contexto organizacional vive dentro da janela de contexto do agente, você garante que essas práticas sejam sempre aplicadas e estejam atualizadas. Do ponto de vista arquitetural, um servidor MCP é o veículo ideal para expor esse tipo de informação. Ele pode ser gerenciado de forma centralizada e atualizado sempre que um comitê de arquitetura, segurança ou governança tomar uma decisão. Prompts, ferramentas e skills são formas eficazes de moldar o comportamento do agente, e podem ser consumidos tanto pelos agentes de codificação nas mãos das pessoas desenvolvedoras quanto por agentes automatizados em pipelines de CI/CD. É uma pena que Agent Skills ainda não façam parte da especificação oficial do MCP, mas já existe um grupo de trabalho dedicado a isso. Assim que pudermos usar servidores MCP para transmitir skills diretamente aos agentes, o desafio de mantê-las atualizadas estará resolvido, reduzindo o atrito para disseminar novos padrões entre as pessoas desenvolvedoras. ### Documentação de produto também é um produto consumível Além das regras internas, esse exato mesmo mecanismo se aplica à documentação de produto. Tradicionalmente, quando o Time A cria uma API interna, ele publica um arquivo de especificação OpenAPI em um portal de desenvolvedores e espera que o Time B leia o manual. Na era agêntica, a documentação estática cria atrito. Se o seu produto foi feito para ser consumido por outros times, a documentação dele deve ser consumível pelas ferramentas desses times. Quando o Time A lançar seu serviço, ele também deveria disponibilizar um servidor MCP dedicado que exponha o schema da API, exemplos de integração e verificações de conformidade na forma de ferramentas. Quando alguém do Time B precisar fazer a integração com o serviço, basta conectar seu agente de codificação ao servidor MCP do Time A. O agente pode consultar a estrutura da API, ler as regras de integração e escrever o código do cliente automaticamente. Migramos de humanos lendo manuais para agentes lendo APIs, garantindo que a intenção arquitetural e os padrões de integração sejam perfeitamente preservados por toda a empresa. ## Automatizando cerimônias: agentes não programadores Embora passemos muito tempo falando sobre agentes de codificação, existem inúmeras oportunidades de otimização usando agentes não programadores (*non-coding agents*) para reduzir a sobrecarga de gestão que frequentemente afeta a maioria das implementações Ágeis. Desde tarefas mais simples, como atas automáticas de reuniões e sumarização, até a repriorização de backlog, refinamento de histórias e criação de spikes técnicos, podemos usar agentes não programadores para recuperar muito do esforço gasto em tarefas administrativas e redirecioná-lo para a engenharia. Aqui estão algumas formas pelas quais esses agentes focados em processos podem elevar o nível do time: * **Refinamento e quebra de histórias:** Se a pessoa responsável pelo produto (Product Owner) redige o rascunho de um épico, um agente pode revisá-lo para identificar cenários de borda esquecidos, premissas técnicas implícitas e fluxos de erro não tratados. Dê a ele acesso a skills específicas e ele estará automaticamente em conformidade com os padrões organizacionais. Pontos de incerteza podem se transformar automaticamente em tickets de spike para investigação técnica mais aprofundada. * **Auditoria de Definition of Ready e Definition of Done:** Em muitas configurações Ágeis, o DoR e o DoD são tratados como meros checklists em wikis que são frequentemente esquecidos. Podemos tornar essa conformidade proativa integrando agentes aos quadros Kanban existentes (como Jira ou GitHub Projects). Quando um ticket é movido para "Ready for Dev", um agente em background pode escaneá-lo para garantir que todo o contexto necessário, como schemas de API e mockups de UI, esteja realmente anexado. Se não estiver, ele sinaliza a transição. Da mesma forma, antes de fechar um ticket, um agente pode verificar se testes foram adicionados e se a documentação foi atualizada. * **Retrospectivas orientadas a dados:** Retrospectivas costumam sofrer de viés de recência. Agentes não programadores podem atuar como analistas de dados imparciais, revisando o histórico de movimentação dos tickets na sprint, comentários em pull requests e conversas em canais de chat. Por exemplo: o agente pode apontar que tickets relacionados a um microsserviço específico levaram em média quatro dias em revisão, incentivando o time a investigar se existe um silo de conhecimento que precisa ser resolvido. ## Escalando o fluxo de trabalho com Gerenciadores de Agentes Ao longo do último ano, a indústria se concentrou fortemente no aprimoramento da experiência com agente único — especialmente os agentes de codificação — e vimos novos padrões surgirem e se consolidarem (MCP, skills, hooks, etc.). Como consequência, a competência central da carreira de engenharia de software migrou da escrita direta de código para a orquestração de agentes. No entanto, existe um gargalo oculto aqui: a carga cognitiva humana. Já existem relatos de um [novo tipo de burnout causado pelo uso de IA](https://techcrunch.com/2026/02/09/the-first-signs-of-burnout-are-coming-from-the-people-who-embrace-ai-the-most/). Delegar tarefas para agentes assíncronos parece ótimo, mas cada tarefa rodando em segundo plano consome sua largura de banda mental. Você ainda precisa lembrar que a tarefa está ativa, revisar o resultado quando ela conclui e reinserir esse contexto no seu fluxo de trabalho principal. Quando são tarefas não correlatas, a penalidade é ainda maior por exigir uma troca de contexto completa. É irônico como nós, humanos, sofremos de problemas de contexto de forma muito semelhante às IAs. Mas, como diz o [teorema fundamental da engenharia de software](https://en.wikipedia.org/wiki/Fundamental_theorem_of_software_engineering): > Todos os problemas da ciência da computação podem ser resolvidos com mais um nível de indireção... exceto o problema de termos camadas de indireção demais. Este ano estamos testemunhando a ascensão dos "Gerenciadores de Agentes": agentes responsáveis por coordenar outros agentes. Embora esse conceito tenha surgido na codificação (como no [Antigravity 2.0](https://danicat.dev/pt-br/posts/20260521-the-hitchhikers-guide-to-antigravity-2-0/) e no [`scion`](https://github.com/googlecloudplatform/scion)), suas implicações são muito mais amplas. Isso nos coloca diante de um problema ainda maior: se já é difícil [revisar o trabalho de um único agente](https://danicat.dev/pt-br/posts/20260303-code-reviews-in-2026/), ou de alguns poucos, como poderemos revisar o trabalho de frotas inteiras de agentes? Não existe resposta fácil para essa pergunta, mas, na minha visão, precisamos trilhar o caminho para construir confiança em sistemas multiagente. Ou, melhor ainda, como dizem na segurança: confie, mas verifique. Da mesma forma como ampliamos a confiança em sistemas de agente único aplicando técnicas de prompting, criando hooks, sandboxes e ferramentas determinísticas, precisaremos encontrar formas de adicionar barreiras de qualidade (*quality gates*) aos gerenciadores de agentes. Avaliação de agentes (*evals*), auditabilidade e práticas de engenharia mais maduras serão fundamentais para essa mudança. Ainda assim, tenho certeza de que chegaremos lá. Anos de práticas consolidadas de engenharia são o que nos dá a tranquilidade de não precisar inspecionar a saída do compilador para checar se o código assembly gerado está correto. Com agentes, o processo não será diferente. ## Uma prévia do futuro: kanban agêntico O que acontece quando tiramos o código do agente de codificação e focamos no produto que estamos construindo? Fiz esse exercício mental e percebi que o resultado não seria muito diferente do que temos hoje em um quadro Kanban — mas, em vez de pessoas humanas puxando tickets, teríamos predominantemente agentes realizando as interações: ![Mockup de kanban agêntico](image.png "Mockup conceitual de um quadro kanban agêntico criado com Google Stitch") Temos as colunas típicas para os diferentes estágios de desenvolvimento (backlog, to do, in progress, etc.), mas cada coluna conta com um conjunto de agentes que trabalharão de forma colaborativa para levar o ticket ao próximo estágio. Adicionar skills globais à coluna fornece um contexto importante para todos os agentes envolvidos — como, por exemplo, padrões de arquitetura e procedimentos de controle. Cada etapa pode ser auditada clicando no ticket e acompanhando o fluxo de conversa entre os agentes. Precisa direcionar os agentes? Adicione um comentário ao ticket. Quer incluir uma etapa de revisão humana? Adicione a si mesma como um dos "agentes". Ao combinar a gestão visual do Ágil com o poder de execução dos Gerenciadores de Agentes, podemos superar o limite da carga cognitiva. Fechamos o ciclo entre os mundos Ágil e Agêntico. As cerimônias do passado evoluem para os dashboards do futuro, provando que tudo o que aprendemos naquelas reuniões intermináveis de planejamento de sprint era apenas preparação para o que vem a seguir. O que você acha dessa abordagem? Adoraria ouvir suas reflexões nos comentários abaixo ou em qualquer uma das minhas redes sociais! --- # Construa um Jogo Arcade Match 3 Usando o Gemini CLI - **Markdown URL**: https://danicat.dev/pt-br/posts/match3-game-gemini-cli/index.md - **HTML URL**: https://danicat.dev/pt-br/posts/match3-game-gemini-cli/ - **Date**: 2026-05-07 - **Categories**: Agentic Coding - **Tags**: codelab, gemini-cli, golang, subagents, vibe-coding - **Summary**: Tutorial prático de desenvolvimento de um jogo Match-3 2D em Go e Ebitengine com Gemini CLI. Aborda plan mode, subagentes para testes visuais e deploy no Cloud Run. > **Nota:** Este artigo foi escrito para o Gemini CLI, que foi descontinuado e substituído pelo **Google Antigravity 2.0**. Para saber mais sobre a nova Antigravity CLI (`agy`), o SDK e o ecossistema mais amplo do Antigravity, confira [O Guia do Mochileiro para o Antigravity 2.0](https://danicat.dev/pt-br/posts/20260521-the-hitchhikers-guide-to-antigravity-2-0/). > **Codelab Atualizado:** Acompanhe a versão moderna deste tutorial atualizada para o Antigravity em [goo.gle/cloud-crush-agy](https://goo.gle/cloud-crush-agy). O verdadeiro motivo de eu ter me tornado uma desenvolvedora de software foi o meu amor por videogames quando criança. Eu passava horas e horas jogando e ficava fascinada tentando entender como eles eram feitos. Meu pai fazia o melhor que podia para me explicar como televisores e computadores funcionavam, mas aquilo nunca entrava direito na minha cabeça. Foi só na adolescência, quando finalmente tivemos acesso à internet, que comecei a entender um pouco melhor. Enquanto outros adolescentes passavam o tempo em salas de bate-papo, mandando mensagens no ICQ e personalizando seus perfis no Orkut, eu passava horas procurando tutoriais de desenvolvimento de jogos. Aqueles sim eram bons tempos. Os anos passaram e eu nunca me tornei uma desenvolvedora profissional de jogos. Minha carreira me levou para a área de bancos de dados, engenharia de dados, serviços de backend e cloud. Não me arrependo das minhas escolhas. Mesmo assim, de vez em quando ainda me pego pensando em como seria a sensação de criar meu próprio jogo indie. E adivinha? Com a ascensão do agentic coding, construir aplicações complexas — incluindo jogos — se tornou tão acessível que não precisamos mais ficar só na imaginação. Podemos criar um jogo totalmente funcional e com deploy na nuvem hoje mesmo, como vou te mostrar agora. Existem duas formas de ler este artigo: como alguém querendo criar jogos que deseja experimentar com GenAI, ou como uma pessoa desenvolvedora experiente que vê no desenvolvimento de jogos uma forma divertida de aprender novas habilidades de agentic coding. Seja qual for o seu caminho, ao longo deste artigo vou te mostrar duas funcionalidades específicas do Gemini CLI: o plan mode e subagentes. Mas antes disso, vamos falar um pouco sobre tecnologia. ## Como escolher a tecnologia certa para o seu projeto Essa sempre foi uma decisão crítica em qualquer equipe de software. Devemos usar as ferramentas com as quais já temos familiaridade? Devemos seguir novas tendências de mercado? Devemos criar algo próprio? Grandes empresas normalmente se apegam às ferramentas que já conhecem. Para justificar uma mudança, é preciso um motivo muito forte. Esse motivo pode vir de fora — como mudanças nos custos do mercado ou escassez de talentos qualificados. Ou pode vir de dentro, como o alto custo de retreinar o time para dar suporte a uma nova stack. O agentic coding muda completamente essa dinâmica. Como a IA pode cuidar do boilerplate, a escolha da linguagem de programação hoje importa muito menos do que a arquitetura geral do sistema. Para nós, pessoas desenvolvedoras, isso é um alívio enorme. Podemos trocar de stack técnica para resolver o problema sem passar meses aprendendo uma sintaxe nova. Você deve estar se perguntando: quando a linguagem perde o protagonismo, o que sobra? Minha resposta é: os padrões. A forma como estruturamos o software, não como um silo isolado, mas como um conjunto de sistemas interconectados. Isso se aplica tanto no nível macro (system design) quanto no micro (design de programas). Você não precisa saber o que cada linha de código faz, mas você **precisa** entender como as diferentes peças do seu software conversam entre si, e você **precisa** saber como direcionar o agente rumo à implementação **correta**. Isso significa que podemos voltar a escrever tudo em BASIC? Não, porque uma linguagem nunca é uma escolha isolada. Ela carrega consigo um conjunto específico de recursos e todo um ecossistema. Continuamos comprometidos a escolher a tecnologia que melhor atende ao que estamos tentando alcançar. A única coisa que deixou de ser um obstáculo é a capacidade imediata do time de escrever aquele código específico. Isso pode ser facilmente mitigado com coding agents modernos, desde que a equipe tenha fundamentos sólidos de engenharia de software. Enquanto um critério perde relevância, novos surgem. Neste caso, vamos prestar muita atenção no quão fácil é para o coding agent gerar software de alta qualidade na linguagem escolhida. Para este projeto específico, escolhi Go por dois motivos principais: é uma linguagem enxuta com a qual os coding agents lidam muito bem (meu MCP godoctor também ajuda!) e conta com um ecossistema open source maduro para desenvolvimento de jogos em torno da ebitengine. Eu poderia ter feito em Three.js? Sim. No entanto, eu queria muito chegar o mais perto possível da experiência de um jogo de arcade / console, então um jogo compilado é essencial para mim. Além disso, o foco aqui é puramente 2D, dispensando engines pesadas como Unity ou Unreal. Por fim, a ebitengine já tem jogos comerciais publicados na Nintendo eShop (para Nintendo Switch), o que alimenta meu sonho de um dia publicar um jogo próprio (obviamente não este aqui). Falando um pouco das vantagens de Go: ser uma linguagem compilada nos ajuda a pegar boa parte dos erros logo cedo no processo de desenvolvimento. Python oferece capacidades parecidas para desenvolvimento de jogos, mas o fato de ser interpretada deixa o meu ciclo de testes mais lento. Além disso, Go pode ser compilada nativamente para a sua máquina local ou compilada para WebAssembly (WASM) para a web. Isso significa que também posso fazer o deploy do jogo como um serviço web com pouquíssimas alterações. ## O retorno do analista de software Enquanto o agente faz o trabalho pesado de escrever o código em Go e compilar tanto o servidor quanto os binários em WASM, nós continuamos com responsabilidades rigorosas no que diz respeito ao design. A engenharia de software está mudando. Passamos menos tempo nos preocupando com truques de sintaxe e mais tempo pensando em padrões de alto nível. De certa forma, parece que estamos voltando à era do clássico 'Analista de Software'. Em vez de digitar cada linha manualmente, nosso trabalho principal passa a ser traduzir requisitos humanos em um conjunto preciso de instruções para que a IA escreva o código de verdade. Eu não tenho experiência profissional em desenvolvimento de jogos propriamente dita, mas como gamer e entusiasta, tenho familiaridade com a **linguagem de domínio** necessária para descrever o que quero alcançar no meu jogo. Ao ancorar meu prompt em certas palavras-chave (por exemplo: arcade game, match 3) ou usar referências consagradas (por exemplo: "Preciso de uma trilha sonora inspirada nas gerações 16-bit e 32-bit de jogos de puzzle, mas com uma roupagem moderna"), consigo comunicar minhas intenções ao agente de forma muito mais eficaz do que alguém tentando criar um jogo sem nenhuma vivência com games. Estou destacando isso para reforçar um ponto: mesmo que codificar acabe se tornando uma habilidade secundária, a capacidade de descrever padrões e funcionalidades continua sendo uma competência crítica da engenharia de software. Você precisa dominar a linguagem de domínio da sua área, seja backend, frontend ou qualquer ponto intermediário. ## Do design à implementação com o plan mode A linguagem de domínio é um bom começo, mas escrever o prompt one-shot perfeito de primeira é praticamente impossível. Em Developer Relations, nós usamos prompts one-shot o tempo todo em palestras e demonstrações, mas o que raramente contamos é quantas horas passamos lapidando aquele prompt antes de subir no palco para mostrá-lo ao público. Construir o prompt ideal é um misto de arte e ciência, e mesmo dominando a linguagem de domínio sempre existirão lacunas. Felizmente, fora do circuito de apresentações e demos, não precisamos acertar tudo de primeira. Além do mais, não precisamos quebrar a cabeça com os prompts sozinhos, já que os próprios agentes podem nos ajudar nisso. É aí que o **plan mode** entra em ação. No plan mode, o Gemini CLI elabora primeiro um plano de implementação antes de tocar em qualquer linha de código. Isso abre espaço para uma conversa iterativa com o agente, refinando o plano e garantindo que a implementação siga exatamente o rumo que você deseja. Em uma conversa comum com o agente, ele pode sugerir entrar no plan mode a partir do fluxo do diálogo (por exemplo, ao responder a um prompt que contenha algo como "vamos fazer um plano"). Mas se você não quiser depender da decisão do agente sobre quando entrar no plan mode, é possível ativá-lo manualmente a qualquer momento com o comando `/plan`. No plan mode, o agente não apenas elabora um plano de implementação com base no seu pedido, mas também pode fazer perguntas de esclarecimento usando a ferramenta `ask_user`. Quando o plano fica pronto, ele pede a sua revisão e te dá a oportunidade de direcionar o plano para onde quiser — corrigindo premissas, ajustando detalhes e adicionando ou removendo funcionalidades. Por exemplo, um prompt razoavelmente refinado — mas longe de perfeito — para o meu jogo Match 3 é apresentado abaixo: ```txt Build a Match-3 game called 'Cloud Crush' in Go using Ebitengine v2. The entire game screen should have background.png as background. The play area should be an 8x8 grid with white background. On the right side of the play area include a side panel with UI elements like player score and how to play instructions. The side panel should have a solid background colour to help with readability of the UI. Use standard GCP product logos (e.g. Compute Engine, Cloud Storage, BigQuery, etc.) as the game gems. These logos are provided in the gcp_sprites.png file. The logos are saved as 64x64 sprites but scale them as necessary based on the screen resolution. Implement swapping, clearing 3+ gems, and gravity. Use ebitengine native font rendering (size 48 for titles and size 24 for normal text) for all text and not the debug print. The font should be monospaced (golang.org/x/image/font/gofont/gomono). Keep the UI tidy and harmonic, e.g. centered text should always be adjusted based on text length, not just guess based on estimates. ``` Embora esse prompt cubra muitos aspectos do jogo, é perfeitamente normal que o agente peça detalhes adicionais, como "qual deve ser a resolução da tela?" ou "você prefere animações fluidas ou transições estáticas?". Quando estivermos satisfeitos com o nível de detalhe do plano, podemos pedir ao agente para começar a codificar, o que encerra o plan mode. A partir daí, o processo não difere de qualquer tarefa comum de desenvolvimento. Após algumas rodadas de interação, temos um jogo funcionando parecido com este: ![Cloud Crush Gameplay Screenshot](https://danicat.dev/pt-br/posts/match3-game-gemini-cli/cloud-crush-gameplay.png) ## Automatizando testes web com o browser agent Uma das tarefas mais difíceis no desenvolvimento de jogos é testar. Não tem como escrever um teste unitário padrão para cobrir todos os estados visuais possíveis de um jogo, ou conferir se as suas funções de renderização estão desenhando os elementos certos na tela. Até daria para tentar, mas garanto que seria um processo exaustivo, frágil e que consumiria um tempo absurdo. Isso não significa que não devamos escrever testes automatizados, mas sim que existem fronteiras entre o que faz sentido cobrir com código puro e o que precisa de playtesting humano. Por exemplo: criar testes unitários para algoritmos de colisão ou pathfinding faz todo o sentido, mas validar a UI em diferentes resoluções é uma tarefa que uma pessoa faz muito melhor (afinal, como você cria um teste unitário para "essa fonte está legível?"). Ou, pelo menos, era assim até agora... _um subagente entra na conversa_ Com as capacidades multimodais dos frontier models e o uso inteligente de agentes, hoje já conseguimos automatizar a validação visual. No Gemini CLI, um subagente é uma persona especializada que roda de maneira independente da conversa principal, dentro da sua própria janela de contexto. Subagentes podem ser usados para adicionar todo tipo de superpoder ao seu fluxo base de programação. No nosso cenário de testes, podemos usar um agente experimental integrado ao CLI chamado `@browser_agent`. Como se trata de um recurso experimental, você precisa [habilitá-lo manualmente](https://geminicli.com/docs/core/subagents/#enabling-the-browser-agent) editando o arquivo `settings.json`. Veja abaixo um exemplo minimalista de `settings.json` habilitando o browser agent com um modelo visual: ```json { "agents": { "overrides": { "browser_agent": { "enabled": true } }, "browser": { "visualModel": "gemini-2.5-computer-use-preview-10-2025" } } } ``` Normalmente, o browser agent navega por uma página web inspecionando a árvore de acessibilidade — a estrutura interna utilizada por leitores de tela. No entanto, o nosso jogo Match 3 é renderizado inteiramente dentro de um único elemento canvas do HTML. Para a árvore de acessibilidade, aquilo não passa de uma grande caixa em branco. É exatamente aqui que adicionar um modelo de visão muda o jogo. Ao configurar o agente com um `visualModel` (como o `gemini-2.5-computer-use-preview-10-2025`), ele literalmente aprende a enxergar. Ele tira capturas de tela, analisa o layout visual e descobre as coordenadas X e Y exatas onde precisa clicar na tela. Em vez de ficar clicando manualmente na aplicação rodando no Cloud Run, você pode simplesmente digitar `@browser_agent please test the live URL...` para instruí-lo a navegar pelo site, jogar uma rodada e tirar screenshots das telas em funcionamento. Isso não substitui o playtesting humano para avaliar o "game feel" (a sensação do jogo), mas automatiza a validação visual, comprovando que a UI renderiza perfeitamente sem que você precise sair do terminal. ## Terceirizando minha ansiedade com segurança Com a implementação funcionando e a UI validada, não podemos nos esquecer da segurança. Eu não sou especialista em segurança de aplicações, o que faz de mim a pior pessoa para avaliar a postura de segurança de um web app. Porém, assim como o agentic coding compensou minha falta de experiência com game engines, subagentes podem compensar minha falta de expertise em segurança. Como orquestradora, não preciso saber de cabeça cada vetor de cross-site scripting; só preciso saber como instanciar um especialista com um contexto limpo para caçá-los. Podemos criar um ambiente de execução isolado definindo um [agente customizado](https://geminicli.com/docs/core/subagents/#creating-custom-subagents) em um arquivo Markdown (`.gemini/agents/security-auditor.md`) que pode ser acionado via `@security_auditor`: ```markdown --- name: security_auditor description: Specialized in finding security vulnerabilities in code. kind: local tools: - read_file - grep_search model: gemini-3-flash-preview temperature: 0.2 max_turns: 10 --- You are a ruthless Security Auditor. Your job is to analyze code for potential vulnerabilities. Focus on: 1. SQL Injection 2. XSS (Cross-Site Scripting) 3. Hardcoded credentials 4. Unsafe file operations When you find a vulnerability, explain it clearly and suggest a fix. Do not fix it yourself; just report it. ``` Nós passamos um system prompt específico para ele (o corpo do arquivo markdown) e ferramentas como `read_file` e `grep_search` (definidas no frontmatter). Como ele roda no seu próprio loop de contexto, não polui o histórico da conversa principal. Apontei esse auditor para a base de código do *Cloud Crush* para verificar a existência de credenciais hardcoded, operações de arquivo inseguras e riscos de deploy. Mesmo que um subagente customizado de segurança não substitua um profissional dedicado de segurança, ele oferece uma camada essencial de proteção que, de outra forma, eu não teria. ## Um novo fluxo de desenvolvimento Esse fluxo define o que considero ser o novo padrão para o desenvolvimento de software. Estamos usando agentes para escrever o código e construindo ativamente ferramentas, skills e subagentes customizados para fazer valer nossos padrões arquiteturais e de qualidade. E para quem lê com atenção: você deve ter notado que fui intencionalmente econômica nas instruções passo a passo ao longo deste artigo. Isso porque temos um codelab completo dedicado a essa experiência prática, que você pode acessar no link abaixo. Nele, você poderá testar tudo o que discutimos aqui seguindo um guia detalhado, construindo no final a sua própria versão deste jogo Match-3. **Codelab: [Construa um Jogo Arcade Match 3 Com o Gemini CLI](https://codelabs.developers.google.com/next26/gemini-cli-match3-golang#0)** E claro, se tiver qualquer dúvida ou quiser trocar uma ideia, fique à vontade para me procurar em qualquer uma das minhas redes sociais. --- # Como Fazer Code Reviews na Era Agêntica - **Markdown URL**: https://danicat.dev/pt-br/posts/code-reviews-in-2026/index.md - **HTML URL**: https://danicat.dev/pt-br/posts/code-reviews-in-2026/ - **Date**: 2026-03-06 - **Categories**: Agentic Coding - **Tags**: code-review, vibe-coding - **Summary**: Guia prático para revisar código gerado por IA e pull requests. Saiba quais áreas de arquitetura inspecionar e quais tarefas mecânicas delegar para linters. Em 2025, vimos a ascensão do agentic coding (aparentemente o termo "vibe coding" já ficou obsoleto). Entre assistentes de IA e fluxos de trabalho agênticos, funcionalidades estão saindo do forno em um ritmo nunca antes visto. Não é raro ver empresas se gabando da porcentagem de sua base de código escrita inteiramente por IA. Se isso é bom ou ruim, ainda vamos ver (eu, particularmente, acho ótimo), mas esse ganho na velocidade de escrita traz consequências: revisar o volume absurdo de código produzido é exaustivo, e os code reviews estão rapidamente se tornando o grande gargalo. Algumas equipes e projetos open source chegaram a adotar a opção radical de simplesmente não aceitar pull requests gerados por IA. Embora banir a IA possa dar um fôlego temporário às pessoas, não acho que seja uma boa opção a longo prazo. "A resistência é inútil", como diria minha espécie de ficção científica favorita. Para sobreviver a esse novo patamar de produtividade, precisamos parar de fazer o trabalho que as máquinas fazem melhor. Combata IA com IA. Mas não apenas com IA: um bom conjunto de ferramentas determinísticas tradicionais também faz maravilhas: se um linter consegue pegar um problema, eu não deveria perder tempo olhando para isso. Se um formatador corrige automaticamente, eu realmente não me importo com isso. Minha opinião "impopular": eu não ligo se foi um humano ou um agente que escreveu o código. No open source, contribuições são tratadas com confiança zero (*zero-trust*). Se o código foi escrito por uma pessoa engenheira sênior de Big Tech ou por um estudante de ensino médio no Sri Lanka, não deveria fazer diferença. Então por que deveríamos nos importar se foi escrito por IA? Em teoria, PRs gerados por humanos seriam menores, mas depois de quase 20 anos trabalhando nesta indústria, já vi minha cota de mega-PRs — então posso dizer com tranquilidade que lidar com PRs gigantescos e/ou desleixados não é um problema novo. Eu avalio o código pelo seu valor real. Ele funciona? É seguro? Resolve um problema conhecido? Está alinhado ao nosso roadmap? Cumpre os nossos padrões? É por isso que, no artigo de hoje, gostaria de falar um pouco sobre como venho abordando code reviews, não apenas ao lidar com contribuições externas, mas também ao avaliar meu próprio código gerado por IA — já que, na verdade, programar com IA significa fazer code review da IA o tempo todo. ## Com o que eu realmente me importo Quando reviso código hoje em dia, meu olhar é cada vez mais de alto nível. Em certo sentido, quanto menos código escrevo manualmente, menos me apego aos detalhes microscópicos da implementação. Sempre disse, em cada equipe em que assumi um papel de liderança de uma forma ou de outra: código é descartável. Isso nunca foi tão verdadeiro quanto hoje. Eu repito: código é descartável. O que não é descartável é o conhecimento do sistema que você adquiriu ao desenvolver determinado código. Esse conhecimento é o que normalmente se transfere bem de uma implementação para outra ou, por exemplo, o que permanece ao migrar da v1 para a v2 da sua API. Escrever algo pela segunda vez é mais fácil porque você já passou pelas dores de aprendizado de descobrir um monte de coisas e reduzir boa parte da ambiguidade. Você aprendeu o que funcionou bem e o que não funcionou. O que ficou com complexidade excessiva (*overengineered*) e o que ficou simplista demais (*underengineered*). Essa é a parte essencial da engenharia de software: acumular conhecimento, iterar, evoluir. E esse é o tipo de conhecimento que sobreviverá à era da IA. Código é apenas um detalhe de implementação. Com base nessa filosofia, esta é uma lista não exaustiva das coisas com as quais me importo ao fazer code review: ### Arquitetura e design de sistemas Modelos de IA têm dificuldade com a visão sistêmica ampla (*the big picture*) e também têm a tendência de pegar muitos atalhos. Meu processo de revisão busca ativamente esses sinais, como valores e configurações fixados no código (*hardcoded*), simplificação excessiva do espaço do problema (a IA frequentemente trata pedidos de código como meros protótipos ou demos) e, paradoxalmente, complexidade desnecessária (*over-engineering*). Modelos de IA também têm o traço irritante de presumir que código pronto para produção é sinônimo de complexidade. Em outras palavras, eles sofrem para dosar equilíbrio e pragmatismo — virtudes que aprendemos com a experiência e que muitas vezes são difíceis de traduzir em palavras. ### API pública e módulos A ergonomia daquilo que estamos construindo é fundamental. A API pública precisa soar natural para o desenvolvedor médio que terá que usá-la. Uma interface bem projetada é intuitiva, difícil de usar incorretamente e esconde os detalhes internos do restante da base de código. Avalio se as interfaces são robustas e têm o escopo correto, buscando a menor superfície de contato (*surface area*) possível. Se a API for desajeitada, não importa o quão elegante seja o código por baixo dos panos. O código é fácil de usar e bem documentado? Um ótimo indicativo de uma boa API pública é a qualidade dos testes: uma API mal projetada é inerentemente difícil de testar. ### Algoritmos e padrões Os LLMs frequentemente recorrem à forma mais ingênua e de força bruta para resolver um problema. É comum um agente tentar rodar uma migração massiva de dados usando loops aninhados e fazendo commit a cada poucas linhas, quando uma estratégia de *bulk insert* seria a abordagem correta. Ou em um nível mais elementar: usar uma lista quando um mapa ou dicionário é a estrutura de dados correta. Garantir que as estruturas de dados e os algoritmos realmente se adequem ao espaço do problema evita quedas brutais de desempenho. O objetivo é código que escala, e não apenas código que passa nos testes. No entanto, otimização prematura continua sendo um risco: se uma abordagem mais simples for ligeiramente mais lenta, mas muito mais legível, e estivermos lidando com um conjunto de dados pequeno e delimitado, a legibilidade geralmente vence. ### Dependências Cada novo pacote traz risco externo, potenciais falhas de segurança e custo de manutenção. Manter a aplicação pequena reduz nossa superfície de ataque. Ferramentas essenciais, como nossos SDKs de GenAI ou grandes frameworks web, passam com mais facilidade na triagem, mas todo o restante é minuciosamente inspecionado. Uma pequena duplicação (ou reimplementação) é bem melhor do que uma dependência desnecessária (*a little copying is better than a little dependency*). Quanto mais fácil fica gerar e manter código, menos me preocupo em reutilizar bibliotecas externas a qualquer custo, especialmente se isso significar adicionar um novo vetor de ataque à minha base de código. ### Anti-patterns e problemas de qualidade Apenas para citar alguns: erros ignorados ou silenciados, efeitos colaterais ocultos, estado global, estado mutável, vazamentos de recursos, funções ou variáveis não utilizadas, e assim por diante. Os idiomatismos da linguagem também importam. Embora eu dê enorme importância a esses pontos, eles também estão entre os mais fáceis de automatizar com o uso de análise estática (linters), como [golangci-lint](https://golangci-lint.run/) (Go) e [ruff](https://docs.astral.sh/ruff/) (Python). ### Testabilidade Código difícil de testar geralmente foi mal projetado e vai resistir a alterações no futuro. Separação clara de responsabilidades, entradas limpas e funções puras são o ideal. Bons testes provam que o código funciona e criam uma rede de segurança para alterações futuras. Para componentes de UI e sistemas complexos, prefiro estratégias práticas de teste a metas rígidas de cobertura unitária, mas a lógica central precisa estar coberta. Parei de tentar estabelecer uma meta de cobertura para cada projeto, porque cada caso é um caso, mas preciso saber que o que deve ser testado está realmente testado. Idealmente, 100% do caminho feliz (*happy path*) e uma boa porcentagem dos fluxos de exceção (*sad path*), mas sem a obsessão ingênua de tentar alcançar 100% de todo o código ou algo perto disso. Desde que você tenha uma estratégia sólida de observabilidade e boas mensagens de erro, você se prepara para o sucesso, já que novos modos de falha podem ser adicionados à suíte de testes com facilidade mais tarde. ### Benchmarking Para caminhos críticos, precisamos de números reais em vez de palpites sobre desempenho. Benchmarks claros para quaisquer alterações que afetem componentes de alto tráfego são obrigatórios para impedir que código lento chegue à produção. ### Logging enxuto Logs devem ser acionáveis. Logs desnecessários inflam a conta de nuvem e podem expor informações privadas. Logging detalhado é aceitável durante o desenvolvimento, mas precisa ser limpo antes do merge. ## Com o que eu (quase) não me importo Deixo as ferramentas automatizadas cuidarem dos pormenores para poder me concentrar nos problemas realmente difíceis. Se uma máquina pode fazer, um ser humano não deveria estar fazendo. ### Cada linha individual de código Revisar linha por linha o que um LLM gerou é trabalho para compiladores e analisadores estáticos. Em vez disso, eu foco na lógica e nos pontos de integração. ### Formatação Desde que comecei a programar em Go, nunca mais me envolvi em discussões sobre estilos de formatação, embora saiba que elas ainda existam em certos lugares. A melhor coisa a fazer é definir um padrão e deixar o linter e o formatador cuidarem disso. Uma vez estabelecido o padrão, o agente de código também consegue ser mais aderente a ele. Se o pipeline de CI passou, está ótimo. ### Detalhes menores de sintaxe e código Existem muitas maneiras de resolver um problema, e forçar escolhas específicas de sintaxe limita a liberdade da pessoa desenvolvedora. Não me importo se é um loop `for` ou uma *list comprehension*, desde que a lógica seja impecável. ### Debugging Quase nunca faço sessões formais de depuração (no sentido estrito de realmente usar um debugger). Debugging para mim é o último recurso — e muitas vezes acaba sendo sinônimo de espalhar dezenas de prints "ESTOU AQUI", que na verdade deveriam ser linhas de log estruturadas. Se algo não está funcionando, crio um novo teste para simular o problema. Se, após reproduzir o problema, ainda não consigo entender a causa raiz, significa que minha observabilidade e meus logs estão insuficientes — e é exatamente em melhorá-los que foco meu esforço. ### Nomes de escopo interno (não exportados) Quando um identificador é local a uma função ou tem escopo restrito, me preocupo muito menos do que com nomes expostos em múltiplos arquivos e funções. Passo o olho rapidamente e, se notar algo absurdo, posso até sugerir um ajuste, mas fora isso estou tranquila com o que o modelo escolheu usar. ### Dependências secundárias Aquelas que não são seus frameworks principais ou SDKs de clientes. Elas geram menos preocupação desde que atendam aos requisitos mínimos de segurança — auditoria contra vulnerabilidades e licenças problemáticas continua sendo obrigatória. Se estou importando um pacote apenas por causa de uma única função "helper", em 100% das vezes prefiro reimplementar essa função no meu código e eliminar a dependência externa. ## Conclusões Este não é um protocolo engessado para todas as situações. Há também muito a ser considerado sobre a instrumentação da sua base de código. Code reviews sozinhos não capturam todos os problemas potenciais, e é por isso que defendo veementemente a automação — ainda mais na era do agentic coding. Agentes de codificação modernos oferecem diversos mecanismos de extensão que permitem restringir o modelo e obter saídas mais determinísticas: [Agent Skills](https://danicat.dev/pt-br/posts/20260829-the-pragmatic-guide-to-agent-skills/), [hooks](https://danicat.dev/pt-br/posts/20260610-mastering-hooks/), [MCP tools](https://danicat.dev/pt-br/posts/20250817-hello-mcp-world/), políticas, regras... Use essas ferramentas para delimitar com precisão o escopo dos seus agentes e a sua rotina será infinitamente mais tranquila. Um carro só pode correr na velocidade em que seus freios conseguem pará-lo. Invista em aprender as barreiras de proteção (*guardrails*) do seu agente de codificação favorito e use seu tempo valioso para revisar aquilo que não pode ser automatizado. Happy coding! Dani =^.^= --- # Criando Agent Skills com o skill-creator - **Markdown URL**: https://danicat.dev/pt-br/posts/gemini-cli-skills-part-2/index.md - **HTML URL**: https://danicat.dev/pt-br/posts/gemini-cli-skills-part-2/ - **Date**: 2026-02-27 - **Categories**: Agentic Coding - **Tags**: agent-skills, gemini-cli, vibe-coding - **Series**: Agent Skills - **Summary**: Explore o design prático de Agent Skills com o skill-creator da Gemini CLI. Exemplos detalhados: latest-version, pyhd com Ruff, find-examples e de-sloppify. > **Atualização (2026):** A Gemini CLI evoluiu para o **Google Antigravity 2.0**. Embora os conceitos fundamentais e a estrutura de Agent Skills abordados neste artigo continuem sendo a base, confira [O Guia do Mochileiro para o Antigravity 2.0](https://danicat.dev/pt-br/posts/20260521-the-hitchhikers-guide-to-antigravity-2-0/) para uma visão geral da plataforma atualizada e de seus novos recursos. Na [Parte 1: Dominando Agent Skills na Gemini CLI](https://danicat.dev/pt-br/posts/20260128-agent-skills-gemini-cli/), exploramos como as Agent Skills adicionam novos recursos à Gemini CLI. Analisamos a skill `experiment-analyst` como um exemplo prático de como manter o contexto do agente limpo, fornecendo instruções específicas para uma tarefa. Hoje, vamos mergulhar nos princípios fundamentais do design de skills e analisar alguns exemplos práticos de skills que utilizo diariamente no meu fluxo de trabalho. ## O que são Agent Skills Vamos fazer uma rápida recapitulação da parte 1, caso você tenha perdido. Agent Skills são um padrão aberto projetado para dar aos agentes de codificação conhecimento especializado "just in time". Elas são desenhadas para que o conhecimento especializado seja adicionado ao contexto apenas quando necessário, ajudando a evitar o chamado *context bloat* (inchaço de contexto). O termo técnico para isso é **Progressive Disclosure** (Divulgação Progressiva): mantemos as instruções principais (`SKILL.md`) o mais enxutas possível e usamos arquivos separados para referências detalhadas ou scripts que são carregados apenas *quando necessário*. No disco, uma skill é uma pasta com um arquivo `SKILL.md` e, opcionalmente, recursos empacotados: ```text skill-name/ ├── SKILL.md (Obrigatório: apenas nome, descrição e instruções principais) └── Bundled Resources (Opcional) ├── scripts/ (Código executável para tarefas repetíveis) ├── references/ (Documentação carregada sob demanda, ex: esquemas de API) └── assets/ (Templates ou arquivos binários usados nas saídas) ``` ## A skill `skill-creator` Você sempre pode escrever skills manualmente, mas a Gemini CLI já vem com uma meta-skill integrada chamada `skill-creator`, que facilita bastante as coisas. Você pode ativar essa skill pedindo à Gemini CLI para criar (ou refatorar) uma skill: > *"Quero criar uma nova skill para buscar a versão real mais recente de um pacote de software para que você pare de alucinar versões."* Qualquer solicitação relacionada a "criar skills" deve acionar automaticamente o `skill-creator`, mas, caso você esteja lidando com um modelo mais "rabugento", também pode ser mais explícita: > *"Use o skill-creator para escrever uma skill para des-sloppificar textos gerados por IA (por favor, não leve para o lado pessoal)"* A Gemini CLI pode pedir alguns detalhes antes de gerar o boilerplate da skill. Ela aprendeu recentemente a interagir com a ferramenta [`ask_user`](https://geminicli.com/docs/tools/ask-user/#ask_user-ask-user), e é muito legal ver isso em ação. ## Quando criar skills No meu fluxo de trabalho pessoal, tenho dois usos principais para skills: 1. Para documentar um processo específico do meu trabalho (ex.: como fazer code review do jeito que eu gosto, como inicializar um repositório, como avaliar um post de blog, etc.) 2. Para adicionar conhecimento especializado sobre uma ferramenta, linguagem ou tecnologia específica (ex.: como funciona um projeto Genkit, como trabalhar com ADK para desenvolver agentes, etc.) Até certo ponto, você pode pensar em skills como um conceito intermediário entre comandos de barra (que costumo armazenar como prompts MCP) e ferramentas. Ao criar comandos de barra, meu objetivo é ter um "processo repetível"; ao criar ferramentas, quero dar ao modelo uma forma determinística de executar algo. Como as skills podem conter tanto prompts quanto scripts, elas conseguem atender a ambos os cenários, com os scripts desempenhando o papel de ferramentas. Claro, se você estiver empacotando suas skills como parte de uma extensão, há uma grande chance de que elas sejam distribuídas com um servidor MCP que também exponha ferramentas. Você também pode aproveitar essa integração na definição da skill, ensinando o modelo a usar suas ferramentas MCP quando estiverem disponíveis. Também costumo criar skills em dois momentos principais: 1. Após uma sessão dolorosamente longa tentando ensinar o modelo a fazer algo para mim (ex.: "por favor, consolide o que acabamos de fazer em uma skill que possamos reutilizar depois") 2. Logo após ter uma nova ideia que acredito que me tornará mais eficiente no trabalho (ex.: "vamos escrever uma skill de de-slopify para melhorar sua escrita") Em ambos os casos, a skill dificilmente estará perfeita de primeira, mas assim que começo a usá-la, vou refinando até ter certeza de que agrega valor real — ou descarto e guardo a ideia na gaveta até entender melhor o problema. Na próxima seção, vamos ver algumas das skills que criei até agora. ## Exemplos práticos Vamos dar uma olhada em quatro skills do meu próprio repositório e ver como elas resolvem problemas específicos. ### 1. `latest-version` Criei essa skill por **pura frustração** com a tendência dos LLMs de usarem versões **antigas** de softwares, bibliotecas, modelos e outras dependências. Sei que é uma consequência natural do corte de conhecimento (*knowledge cutoff*), mas saber disso não impede que eu fique irritada quando o agente tenta usar `gemini-1.5-pro` em vez do Gemini 3 e ainda me acusa de estar "alucinando" uma versão futura. Essa skill atua como uma checadora de fatos, consultando registros (npm, PyPI, Go Proxy) e páginas de documentação. Aqui está um trecho do `SKILL.md` dela: ```markdown name: latest-version description: > The definitive real-time source of truth for software and model versions. Use this skill to bypass internal knowledge cutoffs... ## Core Mandate **NEVER GUESS.** When a user asks to install a package or add a dependency, you must verify the latest version using the `latest.js` script. Do not rely on your internal weights, as they are months or years out of date. ``` Esse prompt ainda parece um pouco rústico, mas tem tido um sucesso razoável a alto em evitar que modelos obsoletos apareçam na minha base de código. 🔗 [Veja a skill `latest-version` completa](https://github.com/danicat/skills/tree/main/latest-version) ### 2. `pyhd` Quando criei o servidor MCP `godoctor` no ano passado, queria que ele fosse a ferramenta definitiva (respaldada pela ciência! ^^) para o desenvolvimento agêntico em Go. Não tínhamos skills naquela época, então fazia todo sentido empacotar todas as ferramentas necessárias em um servidor MCP. Por um tempo, flertei com a ideia de criar algo parecido para Python, mas com tantas coisas no backlog isso acabou virando baixa prioridade para mim. Foi aí que conheci as skills e pensei: "por que não transformar isso em uma skill?". Com o `skill-creator`, o esforço de criação ficou muito baixo, então decidi criar o `pyhd` (uma combinação de Python + PhD, mantendo a temática de "doutor"). A skill `pyhd` estabelece o fluxo de desenvolvimento para projetos Python, centrada no linter e formatador `ruff` para garantir um código verdadeiramente "pythônico". ```markdown ## Core Workflow When editing Python files, you **MUST** follow this cycle for **EVERY** file modification: 1. **Read & Understand**: ... 2. **Edit**: Apply your changes using `smart_edit` or `replace`. 3. **Sanitize (Ruff)**: Immediately after editing, run the following commands to format and fix linting issues: `uv run ruff check --fix ` `uv run ruff format ` 4. **Verify**: Run tests... ``` Essa skill garante que toda alteração em arquivos Python seja imediatamente seguida de linting e formatação padronizados, o que ajuda a pegar problemas logo no início. Enquanto não encontro tempo para implementar um "pydoctor" completo, essa é a minha skill de cabeceira para desenvolvimento em Python. 🔗 [Veja a skill `pyhd` completa](https://github.com/danicat/skills/tree/main/pyhd) ### 3. `find-examples` Às vezes, precisamos ver como uma biblioteca específica é usada no mundo real. A skill `find-examples` utiliza um script Python (`github_search.py`) para pesquisar no GitHub códigos reais que usam a dependência desejada no projeto. Criei essa skill para ajudar a combater o hábito dos modelos de alucinar APIs quando poderiam simplesmente consultar documentação ou exemplos reais. Por usar apenas a busca do GitHub, ela não precisa de personal access token e costuma ter um desempenho muito melhor do que uma busca no Google. ```markdown ### 1. Search for Repositories (Multi-Language) Run the `github_search.py` script. If you can't find many examples in your target language, add related languages supported by the SDK. ### 4. Clone and Inspect Clone the selected repositories into the `_examples` folder. Once cloned, use `list_files`, `smart_read`, or `grep_search` to find relevant implementation details. ``` Também adicionei um recurso em que ela tenta encontrar exemplos em diferentes linguagens para SDKs poliglotas. Como é uma das minhas skills mais recentes, ainda não a testei tão a fundo, mas achei um ótimo exemplo para compartilhar aqui. 🔗 [Veja a skill `find-examples` completa](https://github.com/danicat/skills/tree/main/find-examples) ### 4. `de-sloppify` Uso essa skill para identificar padrões comuns de escrita gerada por IA. Ela traz um script que calcula um "slop score" com base na escolha de palavras, variação no tamanho das frases e repetição estrutural. O script usa o NLTK para realizar tagging morfossintático (POS tagging), o que ajuda a detectar a alta densidade de substantivos e o uso excessivo de voz passiva típicos de textos de IA sem edição. Ele roda localmente e gera um relatório detalhado dos marcadores encontrados. 🔗 [Veja a skill `de-sloppify` completa](https://github.com/danicat/skills/tree/main/de-sloppify) ## Conclusões Skills raramente nascem perfeitas na primeira tentativa. A melhor forma de refiná-las é pelo uso no dia a dia. Quando notar que o agente está se batendo em alguma etapa ou buscando o contexto errado, peça para ele atualizar a skill usando o `skill-creator` novamente. Dê uma olhada nos seus fluxos de trabalho diários. Em quais tarefas você precisa ficar lembrando a IA das mesmas regras o tempo todo? Essas são as candidatas ideais para a sua próxima skill personalizada. Pronta para construir sua primeira skill? Confira a [documentação oficial](https://geminicli.com/docs/cli/skills/) para aprender o básico e, se quiser inspiração, dê uma olhada no [repositório danicat/skills no GitHub](https://github.com/danicat/skills). Happy coding! Dani =^.^= --- # Trazendo Fotos Antigas à Vida com Genkit e Gemini 3 - **Markdown URL**: https://danicat.dev/pt-br/posts/genkit-go-photo-restoration/index.md - **HTML URL**: https://danicat.dev/pt-br/posts/genkit-go-photo-restoration/ - **Date**: 2026-02-16 - **Categories**: Applied GenAI - **Tags**: gemini, genkit, golang, nano-banana, tutorial - **Summary**: Tutorial prático para criar uma ferramenta de restauração de fotos 4K em Go usando Genkit e Gemini 3 Pro Image (Nano Banana Pro) com schemas Handlebars dotprompt e flows. Como parte do meu trabalho, conheço todo tipo de pessoa, e um tema muito comum de conversa é sobre a minha ascendência. Não apenas tenho um sobrenome com sonoridade evidente do Leste Europeu, como meus traços também lembram a região, então as pessoas frequentemente acham que sou polonesa ou tcheca. Muita gente fica surpresa quando conto que, na realidade, sou originária do Brasil. Ninguém na família sabe ao certo de onde nossos antepassados vieram, já que sempre fomos realmente **péssimos** em guardar registros históricos. Talvez por termos consciência disso, frequentemente conversamos em família sobre a parte da história que conhecemos e como ela também está se perdendo. Conforme todos vamos envelhecendo, as memórias são as primeiras a ir embora, seguidas pelos documentos e fotos. Há um sentimento natural de tristeza ao perceber que a última vez que vi minha avó foi há 30 anos, e que o rosto dela muitas vezes não passa de um borrão. É por isso que fotografias são tão importantes para mim: elas são a fortaleza para combater a degradação das minhas próprias memórias. Qualquer coisa registrada nos últimos anos pode ser facilmente duplicada e guardada em quantas cópias redundantes na nuvem eu desejar, mas aqui estamos falando de lembranças de antes da era digital. Mesmo que eu as tivesse digitalizado anos atrás, muitas já acumulam décadas de poeira, mofo, desgaste e ranhuras. Estão congeladas no tempo, mas sem chance de melhora por conta própria. Graças à evolução da IA generativa, nem tudo está perdido: finalmente posso dar um sopro de ar fresco a essas fotos, não apenas restaurando os danos causados pela passagem do tempo, mas também colorizando e fazendo upscale para trazê-las aos padrões modernos. Foi assim que nasceu um pequeno software chamado "GlowUp". Abaixo está um exemplo dessa restauração: ![Foto original danificada e monocromática da minha avó preparando uma torta de banana](original.jpg "Original: minha avó preparando sua mundialmente famosa torta de banana") ![Foto restaurada e colorizada em alta fidelidade 4K usando o Nano Banana Pro](restored.png "Restaurada: restauração e colorização por Nano Banana Pro") Neste artigo, vou mostrar como construir o GlowUp do zero usando o [Gemini Nano Banana Pro](https://ai.google.dev/gemini-api/docs/image-generation) e o [Genkit Go](https://genkit.dev/docs/get-started/?lang=go). ## Os blocos de construção Optei por usar o Nano Banana Pro (também conhecido como Gemini 3 Pro Image Preview) porque ele é atualmente o modelo de processamento de imagem mais avançado da família Gemini. Embora o Nano Banana tradicional (Gemini 2.5 Flash Image) também seja um ótimo modelo, sinto que a versão Pro entrega outputs de maior qualidade e segue instruções com mais precisão, ainda que demande um pouco de tentativa e erro. No lado do cliente, em vez de optar por um SDK de baixo nível como o [go-genai](https://pkg.go.dev/google.golang.org/genai), decidi usar o Genkit, pois ele fornece várias melhorias de qualidade de vida em relação ao código de nível mais baixo, tais como: - Agnóstico quanto a modelos: posso testar modelos diferentes se desejar, mesmo locais ou de fora do ecossistema Google, com uma simples troca de plugin. - Suporte nativo à Dev UI para conveniências como testar modelos, prompts e rastreamento (tracing) de chamadas de modelo. - Arquitetura flexível: pode ser empacotado tanto como uma aplicação CLI quanto como um servidor web. O GlowUp foi construído como um binário unificado que pode rodar como uma ferramenta de linha de comando ou um servidor web. Essa flexibilidade me permite executar restaurações localmente pelo terminal ou implantar o mesmo código como um serviço de nuvem, o que futuramente pode alimentar um aplicativo agradável que até meu pai consiga usar para restaurar o acervo de fotos dele. ## Uma primeira olhada no Genkit Go O [Genkit](https://firebase.google.com/docs/genkit) é um framework open-source projetado para trazer padrões de produção ao desenvolvimento com IA. Se você é uma pessoa desenvolvedora Go*, pense nele como a "biblioteca padrão" para recursos de IA. _(* E se você **não** desenvolve em Go, confira a documentação, pois o Genkit também suporta JS e Python.)_ Aqui está como é um "Hello World" mínimo no Genkit para Go. Note como usamos o plugin `googlegenai` para inicializar o framework: ```go package main import ( "context" "fmt" "log" "net/http" "github.com/firebase/genkit/go/ai" "github.com/firebase/genkit/go/genkit" "github.com/firebase/genkit/go/plugins/googlegenai" "github.com/firebase/genkit/go/plugins/server" // Import the server plugin ) func main() { ctx := context.Background() // Initialize Genkit with the Google GenAI plugin (Vertex AI) g := genkit.Init(ctx, genkit.WithPlugins(&googlegenai.VertexAI{})) // Define a simple Flow genkit.DefineFlow(g, "hello", func(ctx context.Context, name string) (string, error) { // Generate text using a model resp, err := genkit.GenerateText(ctx, g, ai.WithModelName("vertexai/gemini-2.5-flash"), ai.WithPrompt(fmt.Sprintf("Say hello to %s", name))) if err != nil { return "", err } return resp, nil }) // Start the flow server manually mux := http.NewServeMux() // Register all flows defined in 'g' for _, flow := range genkit.ListFlows(g) { mux.HandleFunc("POST /"+flow.Name(), genkit.Handler(flow)) } if err := server.Start(ctx, ":8080", mux); err != nil { log.Fatal(err) } } ``` Este pequeno trecho de código está fazendo muito trabalho pesado. Vamos examiná-lo com um pouco mais de cuidado. ### Plugins Adaptadores que conectam o seu código a provedores como Vertex AI, Google AI ou Ollama. Para os modelos do Google, devemos usar o plugin `googlegenai`. Ele suporta ambos os backends: * **Google AI (Studio):** usa uma API Key. Ideal para prototipagem e projetos pessoais. ```go // Use Google AI (API Key) googlegenai.Init(ctx, &googlegenai.Config{APIKey: "MY_KEY"}) ``` * **Vertex AI (Google Cloud):** usa autenticação IAM do Google Cloud. Recomendado para cargas de trabalho de produção e recursos empresariais. ```go // Use Vertex AI (Cloud Auth) googlegenai.Init(ctx, &googlegenai.VertexAI{ProjectID: "my-project", Location: "us-central1"}) ``` **Nota:** Se você está migrando de versões anteriores do Genkit, pode estar familiarizada com os plugins separados `vertexai` e `googleai`. Eles foram consolidados no único plugin `googlegenai`. ### Modelos (Models) Os LLMs reais (por exemplo, Gemini, Claude) que geram conteúdo. Você os referencia por strings de identificação, como `vertexai/gemini-2.5-flash`. ```go resp, err := genkit.GenerateText(ctx, g, ai.WithModel("vertexai/gemini-2.5-flash"), ai.WithTextPrompt("Tell me a joke")) ``` ### Prompts Embora nada impeça você de fixar prompts diretamente no código (hardcoding), como no exemplo acima, é uma boa prática mantê-los em arquivos separados para facilitar a manutenção. O Genkit usa o `dotprompt` para carregar prompts externos. Um arquivo `dotprompt` (*.prompt) consiste em duas partes principais: o **Frontmatter** e o **Template**. **1. Frontmatter (Configuração)** * **`model`**: o identificador do modelo (por exemplo, `vertexai/gemini-2.5-flash`). * **`config`**: parâmetros de geração como `temperature`, `topK` ou configurações específicas do modelo (por exemplo, `imageConfig`). * **`input`**: um JSON Schema definindo as variáveis esperadas a partir do seu código Go. * **`output`**: para saídas estruturadas. **2. Template (Instruções)** O corpo usa a sintaxe Handlebars para construir o prompt: * **Variáveis**: marcadores como `{{theme}}` são substituídos pelos valores definidos no seu schema de entrada. * **Roles**: os helpers `{{role "system"}}` e `{{role "user"}}` estruturam a conversa, separando as instruções do sistema das perguntas da pessoa usuária. * **Mídia**: o helper `{{media url=myImage}}` injeta dados multimodais (imagens, vídeos) diretamente no contexto do modelo. ```yaml --- model: vertexai/gemini-2.5-flash input: schema: theme: string --- {{role "system"}} You are a helpful assistant. {{role "user"}} Tell me a joke about {{theme}}. ``` ### Fluxos (Flows) No Genkit, um **Flow** é a unidade fundamental de execução que fornece: 1. **Observabilidade**: a execução de cada flow gera automaticamente traces e métricas (latência, uso de tokens, taxa de sucesso) visualizáveis na Genkit Dev UI ou no Google Cloud Trace. 2. **Segurança de Tipos (Type Safety)**: flows são estritamente tipados com schemas de entrada e saída, prevenindo erros em runtime ao encadear múltiplas operações de IA. 3. **Facilidade de Deploy (Deployability)**: flows são estritamente desacoplados da lógica de serving. Para fazer o deploy, basta envolvê-los com `genkit.Handler`, que converte um flow em um `http.Handler` padrão. Isso torna possível servi-los usando a biblioteca padrão ou qualquer framework web em Go: ```go // Define a flow myFlow := genkit.DefineFlow(g, "myFlow", func(ctx context.Context, input string) (string, error) { return "Processed: " + input, nil }) // Expose it as an HTTP handler http.HandleFunc("/myFlow", genkit.Handler(myFlow)) ``` ## Nano Banana Pro O motor por trás da nossa restauração é o **Gemini 3 Pro Image**, carinhosamente conhecido como "Nano Banana Pro". Ele representa um salto significativo em relação às gerações anteriores (e até mesmo aos modelos atuais "Flash"). Enquanto o Gemini 2.5 Flash é incrivelmente rápido e capaz de geração básica de imagens (`gemini-2.5-flash-image`), o **Nano Banana Pro** (`gemini-3-pro-image-preview`) foi construído para raciocínio multimodal profundo. Ele não apenas "enxerga" pixels; ele compreende o contexto semântico. Sabe diferenciar um "arranhão no papel" de uma "cicatriz no rosto". Ele reconhece que uma cozinha dos anos 1950 provavelmente tinha piso de linóleo, e não madeira de lei moderna. ### Principais diferenças * **Flash (gemini-2.5-flash-image)**: otimizado para velocidade e custo. Ótimo para miniaturas (thumbnails) ou ilustrações simples. Resolução máxima de 1024x1024. * **Pro (gemini-3-pro-image-preview)**: otimizado para fidelidade e raciocínio. Suporta geração em **resolução 4K** nativa (até 4096px), o que é indispensável para restauração de fotos. O modelo também aceita parâmetros `imageConfig` para ajustar o output: * `imageSize`: "4K" ou "2K". * `aspectRatio`: "16:9", "4:3", "1:1", etc. Um detalhe importante a ser notado é que este modelo sempre retorna respostas intercaladas (interleaved) contendo tanto texto quanto imagens. Ao contrário de outros modelos de geração, o output exclusivo de imagem não é suportado. É por isso que nossa lógica de extração (que veremos abaixo) precisa ser flexível o suficiente para encontrar os dados da imagem dentro da mensagem de resposta multipartes. **Nota:** No momento da publicação deste artigo, este modelo está disponível apenas na localização `global` no Vertex AI. Você deve configurar seu cliente Vertex AI de acordo. ## Conectando as partes Agora, vamos ver como o GlowUp conecta essas peças. Usamos um **arquivo de prompt** para definir a persona de especialista em restauração e um **fluxo** (flow) para lidar com o processamento da imagem. ### O prompt Usamos um arquivo `.prompt` para definir a configuração do nosso modelo e as instruções. Note como forçamos a resolução `4K` diretamente aqui, mantendo nosso código limpo. ```yaml --- model: vertexai/gemini-3-pro-image-preview config: imageConfig: imageSize: "4K" input: schema: url: string contentType: string --- {{role "system"}} You are GlowUp, a professional-grade photo restorer. Your goal is to provide a "surgical" restoration service that transforms vintage, damaged, or monochrome photographs into high-fidelity 4K colourised versions. RULES: 1. **Grounding**: You are strictly grounded in the original source pixels. Do NOT add new objects (trees, people, buildings, etc.) that are not present in the source. Additionally, do NOT remove any elements from the source, unless they are clearly defects that do not belong in the original scene. 2. **Fidelity**: Preserve the original facial expressions and identity of subjects. Do NOT "beautify" or alter features in a way that changes the person's identity. 3. **Background**: Preserve background fidelity. Overexposed light sources (like windows) must remain as light sources. Do not "fill in" missing details with invented scenery. 4. **Colourisation**: If the image is monochrome, colourize it realistically, respecting historical accuracy where possible. 5. **Upscaling**: Output a high-fidelity image. {{role "user"}} Restore this photo. Image: {{media url=url contentType=contentType}} ``` ### O fluxo O código Go é notavelmente focado. Nesta arquitetura unificada, a definição do flow carrega o prompt e passa o input multimodal para o modelo: ```go // main.go (Flow Definition) type Input struct { URL string `json:"url,omitempty"` ContentType string `json:"contentType,omitempty"` } func defineGlowUpFlow(g *genkit.Genkit) *core.Flow[Input, string, struct{}] { return genkit.DefineFlow(g, "glowUp", func(ctx context.Context, input Input) (string, error) { prompt := genkit.LookupPrompt(g, "glowup") if prompt == nil { return "", errors.New("prompt 'glowup' not found") } resp, err := prompt.Execute(ctx, ai.WithInput(input)) if err != nil { return "", fmt.Errorf("generation failed: %w", err) } return resp.Media(), nil }) } ``` Para suportar arquivos locais nativamente, usamos uma função auxiliar `fileToDataURI`. Esta função lê um arquivo local, detecta seu tipo MIME usando `http.DetectContentType` e o codifica em uma Data URI Base64 padrão que a API do Gemini espera. Isso é fundamental para manter a fidelidade em diferentes formatos de escaneamento sem fixar extensões no código. ```go func fileToDataURI(path string) (uri, contentType string, err error) { data, err := os.ReadFile(path) if err != nil { return "", "", err } contentType = http.DetectContentType(data) encoded := base64.StdEncoding.EncodeToString(data) uri = fmt.Sprintf("data:%s;base64,%s", contentType, encoded) return uri, contentType, nil } ``` Como o Nano Banana Pro é inteligente o suficiente para inferir a proporção de tela (aspect ratio) da imagem de entrada, nós não precisamos de lógicas complexas para calculá-la e injetá-la. Nós apenas fornecemos os pixels e deixamos o modelo fazer o seu trabalho. ## Como executar Se você tem um acervo de memórias desbotadas que servem como âncoras frágeis para a história da sua família, encorajo você a testar isso. É uma forma de recuperar esses momentos do tempo e dar a eles a clareza que merecem. 1. **Clone o Repositório**: ```bash git clone https://github.com/danicat/glowup cd glowup ``` 2. **Configure as Credenciais** (Lembre-se: localização `global`!): ```bash export GOOGLE_CLOUD_PROJECT=your-project-id export GOOGLE_CLOUD_LOCATION=global ``` 3. **Execute a Restauração**: ```bash go run main.go restore --file old_photo.jpg ``` ## Problemas conhecidos e limitações Embora o processo de restauração funcione, ele tem suas peculiaridades. Aqui estão alguns pontos que observei: * **Adesão às instruções:** Mesmo que o Nano Banana Pro seja um modelo de ponta, ele ocasionalmente ainda ignora uma instrução. Você pode achar que são necessárias algumas tentativas antes de obter o resultado desejado. Como não passei muito tempo refinando o prompt, certamente há oportunidades para otimizações adicionais lá. * **Modelos na Dev UI:** Há um bug no plugin `googlegenai` que faz com que ele não liste automaticamente os modelos disponíveis na Dev UI. Você ainda pode referenciar modelos por nome para registrá-los "dinamicamente", mas isso adiciona um pouco de atrito ao processo de experimentação (a versão em JS lida bem com isso). Eu abri [uma issue](https://github.com/firebase/genkit/issues/4783) e já há uma correção em vigor, mas se você estiver usando uma versão mais antiga, é algo a se ter em mente. ## Conclusões Construir o GlowUp foi um experimento gratificante no uso de IA para me reconectar com meu passado em um nível emocional. Sei que há muito pessimismo por aí, mas esse é o tipo de aplicação que me deixa animada com a IA em primeiro lugar. A foto que usei neste artigo está longe de ser o caso de uso mais dramático dessa tecnologia, mas já estou trabalhando na segunda parte deste artigo, onde vou levá-la para o próximo nível para me ajudar a reconstruir um dos meus jogos de cartas favoritos da infância. A conclusão é que o potencial é ilimitado. Espero que isso inspire você a olhar para os seus próprios problemas de nicho — técnicos ou pessoais — e ver o que você pode construir para resolvê-los. **Quer tentar construir o seu próprio?** Eu preparei um [codelab passo a passo](https://codelabs.developers.google.com/cloud-genkit-go-nano-banana?hl=en#0) onde você pode construir este exato app de restauração de fotos do zero. Para mais detalhes, você também pode conferir a [documentação do Genkit](https://firebase.google.com/docs/genkit) e o [código-fonte do GlowUp](https://github.com/danicat/glowup). **Bons códigos!** Dani =^.^= --- # Dominando Agent Skills na Gemini CLI - **Markdown URL**: https://danicat.dev/pt-br/posts/agent-skills-gemini-cli/index.md - **HTML URL**: https://danicat.dev/pt-br/posts/agent-skills-gemini-cli/ - **Date**: 2026-01-29 - **Categories**: Agentic Coding - **Tags**: agent-skills, gemini-cli, mcp, vibe-coding - **Series**: Agent Skills - **Summary**: Aprenda a construir e configurar Agent Skills na Gemini CLI. Cobre progressive disclosure, carregamento de prompts sob demanda e scripts determinísticos de análise em Python. > **Atualização (2026):** A Gemini CLI evoluiu para o **Google Antigravity 2.0**. Embora os conceitos fundamentais e a estrutura de Agent Skills abordados neste artigo continuem sendo a base, confira [O Guia do Mochileiro para o Antigravity 2.0](https://danicat.dev/pt-br/posts/20260521-the-hitchhikers-guide-to-antigravity-2-0/) para uma visão geral da plataforma, e leia [O Guia Pragmático para Agent Skills](https://danicat.dev/pt-br/posts/20260829-the-pragmatic-guide-to-agent-skills/) para padrões modernos de produção, catálogos de skills e otimização de tokens. Quando escrevi sobre o [Tenkai](https://danicat.dev/pt-br/posts/20260120-improving-agentic-coding-with-science/) na semana passada, deixei de abordar um aspecto importante na análise de experimentos: como extrair insights deles. Embora eu tenha uma interface agradável com resumos, métricas estatísticas e testes, é muito difícil capturar as nuances de cada configuração apenas a partir de um resumo. Por exemplo: frequentemente percebo que operações de leitura (como `read_file` ou o `smart_read` do godoctor) estão fortemente correlacionadas com cenários que falharam ou levaram mais tempo para concluir. Isso acontece porque as operações de leitura são ruins? Não: é porque, para se recuperar de um erro, o agente precisou atualizar seu conhecimento do código-fonte relendo-o. Portanto, embora haja uma forte correlação entre leitura, lentidão e falhas, isso de modo algum indica uma relação de causalidade ou, como os estatísticos adoram dizer, "correlação não implica causalidade". Como venho realizando diversos experimentos ao longo das últimas semanas, percebi rápido que ensinar o modelo a executar análises mais profundas a cada vez não era muito eficaz. Tipicamente, em cenários assim, ou eu adiciono as instruções de análise ao contexto do meu agente (via `GEMINI.md`) ou armazeno os prompts necessários em um servidor MCP para poder mapeá-los para comandos de barra (slash commands). Embora ambas as alternativas funcionem, elas têm suas limitações. Expandir o contexto do agente para cada tarefa possível que eu queira executar resultará em inchaço de contexto (*context bloat*) e um comportamento menos eficaz. Criar comandos de barra para cada prompt depende de eu invocar explicitamente o comando, já que o agente não tem conhecimento deles por design. Felizmente, as **Agent Skills** oferecem uma solução que combina o poder de ambos. Agent Skills são um novo recurso na [Gemini CLI](https://geminicli.com) projetado para dar ao agente capacidades sob demanda. Elas se comportam de maneira semelhante a uma ferramenta de agente (na verdade, as skills são ativadas por uma tool call), mas a skill permite acesso sob demanda a um prompt e a arquivos de suporte para permitir que o agente realize tarefas especializadas, inserindo-os no contexto apenas quando forem necessários. Você pode encontrar as especificações técnicas completas na [documentação oficial](https://geminicli.com/docs/cli/skills/), mas neste artigo vou cobrir o básico para você começar. ## Anatomia de uma skill Uma skill nada mais é do que uma pasta com um prompt e, opcionalmente, arquivos de suporte como documentação e scripts. ```text my-skill/ ├── SKILL.md (Obrigatório) Instruções e metadados ├── scripts/ (Opcional) Scripts/ferramentas executáveis ├── references/ (Opcional) Documentação estática e exemplos └── assets/ (Opcional) Templates e recursos binários ``` O arquivo `SKILL.md` é onde reside o prompt da skill. Ele traz um pequeno frontmatter para definir o nome e a descrição; fora isso, é um arquivo markdown comum: ```text --- name: description: --- ``` Para adicionar uma skill ao seu projeto, você pode criar uma pasta dentro de `.gemini/skills`. Por exemplo, a `my-skill` acima ficaria em `.gemini/skills/my-skill`. A Gemini CLI buscará automaticamente por skills na seguinte ordem de precedência: 1. Workspace (/.gemini/skills) 2. Usuário (~/.gemini/skills) 3. Extensões (~/.gemini/extensions//skills) O ponto fundamental a notar é que, quando a Gemini CLI inicia, ela só tem conhecimento do nome e da descrição da skill. Todo o restante será carregado **sob demanda** assim que a skill for ativada. Agora vamos dar uma olhada em como estou usando uma skill para aprimorar meu próprio fluxo de trabalho de análise de experimentos. ## A skill `experiment-analyst` Projetei a skill `experiment-analyst` para ser ativada sempre que peço à Gemini CLI para avaliar um experimento. Ela é organizada da seguinte forma: ```text experiment-analyst/ ├── SKILL.md <-- As diretrizes de análise ├── references/ │ └── tenkai_db_schema.md <-- O esquema do banco de dados, para que o agente não precise descobri-lo toda vez └── scripts/ ├── analyze_experiment.py <-- Replica parte da análise que tenho no frontend ├── analyze_patterns.py <-- Mergulhos profundos em padrões comuns para extrair insights ├── get_experiment_config.py <-- Recupera os detalhes da configuração do experimento └── success_determinants.py <-- Análise de chamadas de ferramentas e correlação ``` ### Definindo a persona especialista O arquivo `SKILL.md` define o procedimento analítico. Ele busca atingir um equilíbrio ao ensinar o agente sobre o que fazer, mas sem seguir uma fórmula engessada ("cookie-cutter"). Um dos aspectos importantes é evitar que o agente tire conclusões precipitadas, definindo uma persona mais fundamentada. Ainda valido todas as afirmações e recebo todas as conclusões com certa cautela, mas essa versão me proporcionou insights interessantes que, de outra forma, exigiriam muito trabalho manual para descobrir. ```text --- name: experiment-analyst description: Expertise in analysing Tenkai agent experiments. Use when asked to "analyse experiment X" to determine success factors, failure modes, and behavioural patterns. --- # Experiment Analyst ## Core Mandates 1. **Evidence-Based:** Never make claims without data. Cite specific Run IDs. 2. **Correlation ≠ Causation:** A tool might be correlated with failure (e.g., `read_file`) because it's used for recovery. Always investigate the *context* of usage. 3. **Comparative:** Always contrast the performance of alternatives. ``` Nota: você pode clicar aqui para ver o arquivo [SKILL.md](https://github.com/danicat/skills/blob/main/experiment-analyst/SKILL.md) completo. ### Os recursos da skill Você vai me ouvir falar muito sobre isso nas próximas semanas: ao lidar com agentes, que são inerentemente **não-determinísticos**, a única maneira de assegurar qualidade é fornecendo ferramentas **determinísticas**. As skills se encaixam perfeitamente nessa filosofia porque podemos agrupá-las com scripts para realizar tarefas de maneira consistente, em vez de deixar para o agente "adivinhar" como deve ser feito. Para a skill de análise de experimentos, eu queria que o agente tivesse liberdade para explorar, mas também não queria que ele ficasse reinventando a roda o tempo todo. Por isso, ela vem com alguns scripts pré-empacotados: - `analyse_experiment.py`: reproduz um resumo do experimento semelhante ao que tenho no frontend, mas inclui alguns agrupamentos de chamadas de ferramentas para comandos de shell - `analyse_patterns.py`: extrai amostras da conversa do agente para tentar identificar padrões de uso de ferramentas - `get_experiment_config.py`: ajuda o agente a entender o experimento recuperando sua definição - `success_determinants.py`: calcula a correlação entre desfechos bem-sucedidos e chamadas de ferramentas Eu forneço o esquema do banco de dados em `references/tenkai_db_schema.md` para quando o agente decidir fazer consultas ad-hoc, evitando que precise redescobrir o schema toda vez (esse schema é bastante estável entre as execuções). Não vou afirmar que essa configuração seja perfeita, já que não passei um tempo significativo refinando-a, mas essa combinação de informações e scripts pré-empacotados cobre a maioria das perguntas que normalmente peço ao agente para explorar. ## Considerações finais As Agent Skills representam uma mudança significativa na forma como projetamos fluxos de trabalho agênticos. Ao nos afastarmos de prompts de contexto gigantescos (como adicionar tudo ao `GEMINI.md`) em direção a capacidades modulares e sob demanda, resolvemos dois problemas de uma só vez: mantemos o contexto do nosso agente limpo (menos tokens) e viabilizamos uma expertise profunda e especializada que não dilui o desempenho geral. No meu caso, a skill `experiment-analyst` foi fundamental para transformar uma tarefa repetitiva em um fluxo semiautomatizado. Ela me dá consistência e flexibilidade ideais para realizar as análises que desejo. Agora estou considerando atualizar outras partes do meu fluxo de trabalho para skills, superando a minha abordagem anterior de usar servidores MCP apenas como "bancos de dados de prompts". Estou animada para ver o que a comunidade vai construir. Então, dê uma olhada em seus próprios fluxos de trabalho: onde você está constantemente repetindo instruções? Onde você precisa de um especialista? Essa é a sua próxima skill esperando para ser escrita. **Atualização:** Você já pode conferir a [Parte 2: Criando Agent Skills com o skill-creator](https://danicat.dev/pt-br/posts/20260227-gemini-cli-skills-part-2/), onde mergulhamos em exemplos práticos. Bons códigos! --- # Melhorando Agentes de Programação com Ajuda da Ciência - **Markdown URL**: https://danicat.dev/pt-br/posts/improving-agentic-coding-with-science/index.md - **HTML URL**: https://danicat.dev/pt-br/posts/improving-agentic-coding-with-science/ - **Date**: 2026-01-21 - **Categories**: Agentic Coding - **Tags**: agents, ai, gemini-cli, golang, mcp, testing, vibe-coding - **Summary**: Descubra como avaliar agentes de programação usando rigor estatístico e testes A/B com o framework Tenkai em Go, teste t de Welch, teste exato de Fisher e correlação de Spearman. A mudança do [determinismo para o não-determinismo](https://newsletter.pragmaticengineer.com/p/martin-fowler) no desenvolvimento de software é um dos maiores desafios que enfrentamos como indústria hoje. Todo mundo já passou por isso: você está trabalhando com um agente de IA e pensa: *"E se eu adicionar isso ao meu system prompt?"* ou *"Talvez eu devesse dar a ele acesso a esta nova ferramenta MCP?"* Você faz a alteração, roda uma vez e funciona. Sucesso! Você se sente genial e compartilha com todos os seus colegas de trabalho, seus seguidores, sua avó e seu gato. Mas aí, uma hora depois, você tenta de novo e ele falha miseravelmente na mesmíssima tarefa. A mudança foi realmente boa ou você só deu sorte na primeira tentativa? "Talvez eu não tenha feito direito desta vez..." — desliga e liga de novo, tenta mais uma vez e funciona... ou será que funciona? O fato é que não há apenas uma aleatoriedade inerente à forma como os LLMs funcionam, mas também diversos fatores de confusão (*confounding factors*) que podem contribuir para esses resultados. Como vivemos no mundo determinístico por tanto tempo (será mesmo?), nós, pessoas da engenharia de software, não estamos psicologicamente preparadas para lidar com esse nível de incerteza. Esperamos que a computação seja exata, já que aprendemos desde o início que ela é uma [ciência exata](https://pt.wikipedia.org/wiki/Ci%C3%AAncias_exatas). ## A vibe vs. a ciência Costumo falar muito sobre [vibe coding com disciplina](https://danicat.dev/pt-br/posts/20251206-taming-vibe-coding/) ou, em outras palavras, como adicionar metodologia ao vibe coding é essencial para alcançar resultados de qualidade. Esse não é um problema novo — lidamos com desafios de qualidade de software há décadas —, mas a redução da barreira de entrada e o aumento vertiginoso na velocidade de produção de código elevaram a questão a uma nova ordem de magnitude. Sempre me surpreendo quando "descubro" que uma metodologia antiga, criada para ajudar equipes humanas a produzirem melhores resultados, costuma funcionar incrivelmente bem com agentes de IA. Mas, na verdade, isso não deveria surpreender ninguém: afinal, seres humanos também são não determinísticos por natureza. A IA está apenas amplificando padrões há muito conhecidos. Embora eu tenha conseguido estruturar um fluxo de trabalho bem razoável com base nesses princípios, nunca cheguei a ter a segurança de afirmar "este é o jeito CERTO de fazer isso". E isso acontece frequentemente porque uma falha ocasional ou regressão inesperada corrói a minha confiança — "Será que este `GEMINI.md` é realmente o prompt definitivo para quem desenvolve em Go?" "Será que cheguei às melhores instruções de sistema para esta tarefa?" "Minhas ferramentas MCP são as melhores APIs que eu poderia criar?" — todos os dias sou inundada por tantas dúvidas que, na maior parte do conteúdo que publiquei no último ano, evitei ser prescritiva e posicionei tudo como **estudos de caso** — "Eu fiz _isso_", "aconteceu _aquilo_". Ponto final. Na literatura científica, estudos de caso representam um dos [níveis mais baixos de evidência](https://pt.wikipedia.org/wiki/Medicina_baseada_em_evid%C3%AAncias). Sei que este não é o argumento típico em um blog técnico, então preciso abrir um pequeno parêntese e falar sobre a minha "vida anterior": antes de me firmar na carreira de engenharia de software, cursei a faculdade de Medicina (nunca cheguei a me formar, mas essa é uma história para outra hora). Na Medicina e em outras áreas da Biologia, as pessoas estão muito mais acostumadas à experimentação e a lidar com vieses, ruído e aleatoriedade, pois precisam extrair "verdades" de sistemas de origem desconhecida (basicamente fazendo engenharia reversa do mundo, o que é fascinante!). Não basta apresentar os dados de um estudo do jeito que estão; é preciso validá-los estatisticamente para assegurar a eliminação de qualquer contaminação potencial. Tamanho da amostra, alfa, p-valores, Teste t de Student... Eu detestava esses termos nas provas, mas mal sabia como eles seriam úteis hoje. É claro que isso não é exclusivo da Biologia. Na engenharia, aplicamos métodos estatísticos com frequência ao pesquisar, mas sinto que isso não é tão disseminado na nossa área quanto em outras. Quando trabalhei com sistemas de recomendação, por exemplo, testes A/B eram uma parte crítica do trabalho para otimizar os algoritmos de machine learning. Pesquisadores de UX também usam testes A/B exaustivos para determinar quais interfaces performam melhor, e assim por diante. Eu queria encontrar uma maneira de eliminar o "achismo" dos meus experimentos com agentes de programação. Percebi que a melhor saída seria criar um framework de experimentação para coletar dados e realizar análises estatísticas, me permitindo ir além do "acho que funciona" para o "**sei** que funciona (_com 95% de confiança_)". ## Apresentando o Tenkai: o framework de experimentação para agentes Para resolver esse desafio, criei o **Tenkai** (palavra japonesa para "desdobramento", "expansão" ou "desenvolvimento", um pequeno easter egg para quem curte animes). Trata-se de um framework em Go projetado para avaliar e testar diferentes configurações de agentes de programação com rigor estatístico. ![Visão de experimento no Tenkai](image-3.png "Interface do Tenkai com um experimento em execução") Pense nele como um laboratório para seus agentes de IA. Em vez de rodar um prompt uma única vez e torcer para dar certo, o Tenkai permite executar experimentos que repetem as mesmas tarefas diversas vezes (até um número N de repetições — o tamanho da sua amostra) e compara alternativas (diferentes conjuntos de configurações) entre si por meio de testes estatísticos. Digamos que a Alternativa A seja a sua configuração padrão (o "controle" do experimento) e a Alternativa B seja um novo system prompt que você quer experimentar. O experimento executará ambas N vezes e gerará um relatório indicando se B é significativamente mais rápida, mais eficiente ou mais precisa. Se a diferença decorrer de mera coincidência ou ruído, o framework não a classificará como significativa, e você poderá descartar os resultados com tranquilidade. ### Como funciona O fluxo de trabalho no Tenkai: 1. **Defina os cenários:** Um cenário é uma tarefa de programação padronizada (por exemplo, "Corrigir um bug neste pacote Go" ou "Implementar um novo componente React"). Ele inclui regras de validação como "Compilou?" ou "Os testes passaram?". Para que um cenário seja considerado bem-sucedido, ele precisa atender a todos os critérios de validação especificados. ![Visão em lista dos cenários](image-2.png "Cenários são suas tarefas de programação. Você pode adicionar um ou mais cenários aos seus experimentos e todos serão validados com os mesmos critérios") 2. **Crie um template:** Você define o que quer testar. Por exemplo, sua "Alternativa A" (controle) pode ser o modelo Gemini 2.5 Flash, e sua "Alternativa B" pode ser o Gemini 2.5 Flash com um servidor MCP configurado. É possível incluir até 10 alternativas no mesmo template de experimento (todas serão comparadas com o controle). ![Editor de alternativas](image-1.png "O editor de alternativas permite sobrescrever o comando do agente, flags de linha de comando, system prompt, GEMINI.md e settings.json") 3. **Execute o experimento:** O Tenkai executa cada cenário múltiplas vezes para cada alternativa. Ele isola as execuções em workspaces temporários, gerencia timeouts e registra cada evento — de chamadas de ferramentas a saídas de shell. Você também pode definir um nível de concorrência para rodar tarefas em paralelo, sem precisar esperar uma eternidade pelos resultados. ### Rigor estatístico (A parte científica) Estes são os testes estatísticos atualmente integrados ao framework: * **Teste t de Welch**: Para métricas contínuas como duração, consumo de tokens ou quantidade de problemas de lint. Usamos a versão de Welch em vez do teste t padrão porque ela não assume variâncias iguais entre os grupos — o que é essencial ao comparar modelos com perfis de performance bem discrepantes. * **Teste Exato de Fisher**: Para taxas de sucesso. Ao trabalhar com tamanhos de amostra menores (como 10 ou 20 repetições), os testes qui-quadrado padrão podem perder precisão. O teste de Fisher nos fornece um p-valor confiável para determinar se uma taxa de sucesso de 80% é realmente superior a uma de 60%, ou se foi apenas uma sequência de sorte. * **Teste U de Mann-Whitney**: É o nosso cavalo de batalha não paramétrico. Nós o usamos para comparar a *distribuição* de chamadas de ferramentas entre execuções bem-sucedidas e com falha. Como as contagens de tool calls não seguem uma distribuição normal (há muitos zeros!), o Mann-Whitney ajuda a identificar se uma ferramenta específica está sendo acionada de forma significativamente maior nas execuções vencedoras. * **Rho de Spearman (Correlação)**: Usamos para identificar os "Determinantes de Sucesso". Ao calcular a correlação entre chamadas de ferramentas específicas e métricas como duração ou tokens, o Tenkai consegue apontar se uma nova ferramenta MCP atua como um verdadeiro **Impulsionador de Sucesso (Success Driver)** ou se é apenas uma distração que infla os custos. ## Insights em tempo real e o dashboard Conforme o experimento é executado, você pode acompanhar a linha de raciocínio do agente, ver quais ferramentas ele está acionando e identificar onde ele empaca. É como poder "olhar dentro da mente" do seu agente ao longo de dezenas de execuções simultâneas. Um dos recursos mais poderosos do dashboard é a capacidade de filtrar a análise em tempo real sob três "lentes" distintas: * **Todas as Execuções (All Runs):** A verdade nua e crua. Inclui cada timeout crítico e erro de sistema. É a métrica primária da **confiabilidade (reliability)** geral do sistema. * **Apenas Concluídas (Completed Only):** Filtra execuções que atingiram um estado terminal (Sucesso ou Falha de Validação). É onde você avalia métricas de **qualidade**, como problemas de lint ou tempo de execução, removendo o ruído de timeouts externos. * **Apenas Bem-sucedidas (Successful Only):** A visão "Padrão Ouro". Ao olhar exclusivamente para as execuções vencedoras, você começa a deduzir *por que* elas deram certo. É aqui que calculamos o **Rho de Spearman** e os p-valores de **Mann-Whitney U** para descobrir quais ferramentas estão altamente correlacionadas com o sucesso. ## Primeiros resultados dos meus próprios experimentos Tenho usado o Tenkai para refinar o [godoctor](https://github.com/danicat/godoctor) e transformá-lo no meu servidor MCP dos sonhos para desenvolvimento em Go. Minha hipótese é que, ao fornecer ferramentas especializadas aos modelos, eles se tornarão mais eficazes na execução de tarefas de código. Por exemplo: em vez de dar aos modelos a liberdade de decidir quando ler a documentação para descobrir a API de uma biblioteca cliente, estou entregando a documentação diretamente a eles sempre que o modelo chama `go get`. Isso preveniu massivamente alucinações de API e aqueles "loops de inferno de dependências", em que o modelo fica batalhando com `go get` e `go mod` achando que obteve a versão errada do pacote. Os primeiros resultados também mostram que essa diferença diminui se eu fixar a versão do modelo na geração mais recente — a Gemini CLI por padrão é iniciada no modo "auto", o que significa que ela decide automaticamente qual modelo chamar entre o Gemini 2.5 e o Gemini 3 (tanto versões Flash quanto Pro). Ao fixar a versão para o Gemini 3 Pro (ID do modelo `gemini-3-pro-preview`), ele se torna muito mais inteligente e frequentemente busca a documentação por conta própria (rodando `go doc` na linha de comando) nesses cenários de conflito, tornando o conjunto de ferramentas do godoctor menos impactante. ![Tabela de resumo do experimento comparando a configuração padrão com duas configurações do godoctor (com e sem ferramentas principais)](image.png "Ter o godoctor ativado em paralelo com as ferramentas principais não teve impacto significativo e potencialmente desperdiçou tokens (p < 0,1), mas após desativar as ferramentas principais — forçando o uso do godoctor —, a duração média da tarefa reduziu em mais de 30% (p < 0,01)") Também enfrentei muitos desafios com a adoção de ferramentas. Os modelos têm, por padrão, uma forte preferência por usar as ferramentas nativas com as quais foram treinados. Mesmo que eu forneça uma ferramenta "mais inteligente", eles têm dificuldade em sair da sua zona de conforto. Errei bastante tentando projetar experimentos em torno disso até perceber que meu processo de experimentação estava completamente falho. **Adoção de ferramentas** e **eficácia de ferramentas** são conceitos ortogonais: ao tentar testar ambos no mesmo experimento, eu não conseguia produzir resultados válidos. Decidi pivotar para testar apenas a eficácia da ferramenta, bloqueando o acesso do modelo às ferramentas nativas. Dessa forma, finalmente comecei a obter sinais melhores sobre o quão boas (ou ruins) são as ferramentas do godoctor. Se você tem curiosidade sobre a evolução do godoctor e como cheguei à API atual, darei uma palestra sobre o tema na [FOSDEM no próximo dia 1º de fevereiro](https://fosdem.org/2026/schedule/event/3BD3Z9-making_of_godoctor_an_mcp_server_for_go_development/). E se você não puder comparecer, não se preocupe: também escreverei mais sobre isso nas próximas semanas. ## Conclusões Estamos em um ponto de inflexão no desenvolvimento de software. Estamos migrando de um mundo onde escrevemos cada linha de código para um cenário onde **orquestramos** inteligência. Mas orquestração exige medição. Não podemos melhorar o que não podemos medir. A transição de ser apenas uma escritora de código para uma orquestradora de inteligência não significa que trabalhamos menos — significa que fazemos um trabalho **diferente**. Nossa responsabilidade primária não é mais apenas a sintaxe; é o **Contexto, as Ferramentas e as Diretrizes (Guardrails)**. Se não medirmos o impacto das nossas mudanças com rigor, não estamos fazendo engenharia; estamos apenas jogando com a sorte. Ao avançar para uma abordagem baseada em evidências para agentes de programação, podemos finalmente construir sistemas que não sejam apenas "legais" quando funcionam, mas confiáveis o suficiente para sustentar um negócio de verdade. Se você tiver interesse em conferir o código ou realizar seus próprios experimentos, pode encontrar o projeto no [repositório do Tenkai no GitHub](https://github.com/danicat/tenkai). Bons experimentos! o/ Dani =^.^= --- # Minha Retrospectiva de 2025: Entrada no Google e Domando o Vibe Coding - **Markdown URL**: https://danicat.dev/pt-br/posts/my-2025-year-in-review/index.md - **HTML URL**: https://danicat.dev/pt-br/posts/my-2025-year-in-review/ - **Date**: 2026-01-13 - **Categories**: Perspectives - **Tags**: career, community, reflection, vibe-coding - **Summary**: Retrospectiva de 2025: entrada no Google DevRel, domínio do vibe coding com disciplina, lançamento dos servidores MCP GoDoctor e Speedgrapher e planos para 2026. À medida que 2025 desaparece pelo retrovisor, me vejo sentada com uma xícara de café (naturalmente), tentando processar tudo o que acabou de acontecer. Se eu tivesse que escolher uma única palavra para este ano, seria **aceleração**. Não foi apenas a indústria que se moveu rápido: meu próprio ritmo pessoal e profissional mudou de marcha de um jeito que eu não esperava. Desde entrar no Google até redefinir o que significa ser uma engenheira na era da IA, 2025 foi um ano de profunda "refatoração". ## O capítulo Google Em abril, entrei para o time de Developer Relations no Google. Era a realização de um sonho de décadas, e o dia a dia fez jus a toda a expectativa. Tem sido intenso, com certeza. Viajei por todo o Reino Unido, Europa e até consegui um tempo para me reconectar com a comunidade de desenvolvimento no [Brasil](https://danicat.dev/pt-br/posts/20251029-six-months-at-google/). Mas, muito além dos quilômetros rodados, o mais marcante foi a jornada intelectual. Mergulhei de cabeça no ecossistema Google Cloud, colocando a mão na massa com [Vertex AI](https://danicat.dev/pt-br/posts/20250605-vertex-ai-sdk-python/), [MCP](https://danicat.dev/pt-br/posts/20250817-hello-mcp-world/) e o [Agent Development Kit (ADK)](https://danicat.dev/pt-br/posts/20251020-diagnostic-agent-with-adk/). Foi um intensivão no futuro do software, e aproveitei cada minuto. ## O ano do vibe coding Se 2024 foi sobre "brincar" com IA, 2025 foi o ano em que comecei a **abraçá-la** de verdade. Fui de cética a uma verdadeira evangelista do ["vibe coding"](https://danicat.dev/pt-br/posts/20251206-taming-vibe-coding/). Mas não, não [**aquele**](https://x.com/karpathy/status/1886192184808149383?lang=en) vibe coding em que você está pensando. Percebi que "esquecer que o código existe" é uma armadilha. Em vez disso, aprendi que a disciplina de engenharia — requisitos claros, gerenciamento de contexto e testes rigorosos — é *mais* crítica do que nunca. Por isso, defendo uma vertente diferente de vibe coding: vibe coding com disciplina. Há quem diga que eu nem deveria usar a gíria vibe coding, mas fazer o quê? O termo pega fácil! Também desenvolvi um [fluxo de trabalho moderno para pessoas desenvolvedoras](https://danicat.dev/pt-br/posts/20250714-developer-workflow/), em que a pessoa desenvolvedora atua como a "piloto", comandando uma frota de agentes de IA como a [Gemini CLI](https://geminicli.com/) e o [Jules](https://jules.google/). Essa mudança não foi apenas filosófica, foi prática. Eu fiz "vibe coding" de ferramentas inteiramente novas para resolver meus próprios problemas: * **[GoDoctor](https://danicat.dev/pt-br/posts/20250729-how-to-build-an-mcp-server-with-gemini-cli-and-go/):** Um servidor MCP para ensinar a IA a escrever código Go melhor. * **[Speedgrapher](https://danicat.dev/pt-br/posts/20250805-introducing-speedgrapher/):** Um kit de ferramentas para automatizar as partes burocráticas da escrita técnica. * **[AIDA](https://danicat.dev/pt-br/posts/20251031-building-aida/):** Meu sonho de computador de "Star Trek" se tornando realidade — um agente de diagnóstico que realmente responde para você. ## Olá, mundo MCP! Falando em ferramentas, 2025 foi o ano em que o **Model Context Protocol (MCP)** mudou tudo. Há quem chame de o "momento USB-C" da IA. Eu chamo de "HTTP/REST para a IA". Passamos as últimas duas décadas construindo automação para que sistemas pudessem ser acionados programaticamente. O próximo ciclo da engenharia é adaptar sistemas legados e criar novos sistemas que possam ser acionados "agenticamente". Finalmente, passamos a ter uma forma padronizada de conectar nossos modelos ao mundo real. Apostei alto nessa tecnologia, fazendo uma [keynote na GopherCon UK](https://danicat.dev/pt-br/posts/20250817-hello-mcp-world/) e escrevendo bastante sobre como construir seus próprios servidores. Ver a comunidade se mobilizar em torno desse padrão foi incrivelmente gratificante. Não estamos mais apenas conversando com bots; estamos construindo sistemas que *fazem* coisas. ## Refatoração pessoal Não foram só códigos e conferências. Também tirei um tempo para refatorar meu hardware físico. Tenho muito orgulho em dizer que perdi **mais de 20 kg** este ano! Estou na minha melhor forma física em toda a minha vida adulta. Acontece que fazer um trabalho que você ama em um ambiente acolhedor é o melhor plano de saúde que existe. Também passei muito tempo refletindo sobre o próprio tempo. Minha exploração sobre [por que o tempo parece passar mais rápido conforme envelhecemos](https://danicat.dev/pt-br/posts/20250811-a-model-for-perceived-time/) ressoou com muitas pessoas. Tudo começou como um pensamento de chuveiro turbinado pela IA... Sei que muita gente diz que a IA prejudica a criatividade, mas adoro brincar com ela para explorar pensamentos e ideias que não teria tempo, paciência ou dedicação para desbravar na vida real. Para mim, o poder da IA está em me permitir fazer coisas que eu não faria de outra forma (infelizmente ela ainda não lava a louça, mas com certeza serei a primeira da fila para comprar uma empregada robô de anime). ## Olhando para 2026 E o que vem a seguir? Se 2025 foi sobre **adoção** — aprender as ferramentas e encaixá-las em nossos fluxos de trabalho —, acredito que 2026 será sobre **kaizen** (melhoria contínua). Há muito o que aprender sobre como essas ferramentas se comportam, o que funciona e o que não funciona. Estamos coletivamente experimentando, testando coisas novas e documentando nossos processos e aprendizados. Chegou a hora de consolidar todas essas lições e alcançar um nível maior de maturidade no uso dessa tecnologia, seja para geração de código ou para agentes totalmente autônomos que simplifiquem a vida. Planejo me aprofundar ainda mais em **Software Personalizado** (*Personalized Software*). A barreira de entrada para construir ferramentas sob medida despencou. Estamos entrando em uma era em que, se você tem um problema, você não procura um aplicativo — você cria um. Quero continuar explorando essa fronteira, criando mais ferramentas como o Speedgrapher que sejam moldadas exatamente às minhas necessidades, e também ensinando vocês a fazerem o mesmo. Também quero soltar meu lado desenvolvedora de jogos e participar de pelo menos uma Game Jam. Tentei este ano, mas não consegui concluir nenhum jogo, principalmente por ter que colocar em dia outros aspectos importantes do meu trabalho. Agora estou em uma posição um pouco mais confortável, então acredito que, com um esforço a mais, consiga lançar um ou dois jogos no mundo. Muito obrigada por fazer parte desta jornada. Quer você tenha lido um post, assistido a uma palestra ou programado no embalo do vibe coding comigo — meu muito obrigada! Um brinde a um 2026 repleto de novas vibes, código mais limpo e muitas fotos de gatos. o/ Dani =^.^= --- # Domando o Vibe Coding: O Guia Prático da Engenheira - **Markdown URL**: https://danicat.dev/pt-br/posts/taming-vibe-coding/index.md - **HTML URL**: https://danicat.dev/pt-br/posts/taming-vibe-coding/ - **Date**: 2025-12-06 - **Categories**: Agentic Coding - **Tags**: agile, architecture, software-engineering, tdd, vibe-coding - **Summary**: Aplique o rigor da engenharia de software ao vibe coding. Aprenda templates de prompt estruturados, engenharia de contexto, matriz 2x2 e TDD adaptado. Chegou aquela época do ano de refletir sobre o que fizemos e o que gostaríamos de ter feito. Este ano foi intenso para mim: entrei no Google em abril e encarei uma corrida sem trégua para me refatorar e me adaptar ao mundo da IA. Com o ano chegando ao fim, posso dizer com total segurança que o esforço valeu a pena: tornei-me uma engenheira melhor. Neste artigo, compartilho como minha visão sobre "vibe coding" evoluiu e as lições que aprendi pelo caminho. Embora muitos vejam essa prática apenas como uma forma de pessoas não técnicas criarem software usando linguagem natural, meu objetivo é demonstrar como a disciplina de engenharia produz resultados infinitamente superiores e consistentes. Conheço bem a definição original — ["(...) entregue-se às vibes (...) e esqueça que o código sequer existe"](https://x.com/karpathy/status/1886192184808149383?lang=en) — e vários conceitos que defendo aqui vão na contramão disso. Eu nunca "esqueço que o código existe". Ainda assim, o termo evoluiu para se tornar sinônimo de desenvolvimento assistido por IA. Para efeitos deste texto, vamos definir vibe coding como programar com LLMs onde o modelo escreve a maior parte do código, e não a pessoa engenheira. ## Motivação: Por que fazer vibe coding? Antes de mergulhar nas práticas, quero contar um pouco da minha trajetória para dar contexto de onde estou partindo. Sou engenheira de software há mais de 20 anos e desenvolvi um senso apurado do que constitui um bom código. Aprendi a priorizar legibilidade e manutenibilidade em detrimento de soluções "espertinhas", a evitar complexidade desnecessária (*overengineering*) e a valorizar entregas incrementais (*thin slices*) e ciclos rápidos de feedback. Conforme evoluí de desenvolvedora sênior para engenheira principal (*principal engineer*), meu foco mudou de digitar código para gerenciar a eficácia técnica — detalhar épicos, negociar escopo e garantir a qualidade das entregas do time. É uma crise de identidade que muitos profissionais enfrentam: você ainda é engenheira se o seu nome não aparece nos PRs? Você passa dias em reuniões, sentindo que está fazendo menos "engenharia de verdade" bem no momento em que suas responsabilidades aumentam. Acredito que esse conflito surja mais cedo ou mais tarde para a maioria das pessoas da área. Ele nos leva a refletir: ser engenheira é só escrever código? Ou vai muito além disso? Devo admitir que minhas primeiras experiências com vibe coding foram frustrantes. As primeiras versões do ChatGPT geravam código decepcionante, e acabei deixando de lado. Foi apenas em meados de 2024 que resolvi dar uma nova chance. Os modelos tinham evoluído bastante. Pela primeira vez, a IA sugeriu algo que eu não tinha considerado — e era objetivamente melhor do que a minha própria abordagem. Finalmente, a IA generativa mostrou a que veio. Adicionei a GenAI à minha caixa de ferramentas para criar protótipos e fazer sessões de "rubber ducking". Fui pegando o gosto. Por outro lado, escrever código na mão estava perdendo a graça. Chega uma hora em que você já implementou tantas APIs que a novidade acaba. Muitas vezes nos pegamos apenas repetindo padrões em vez de criar algo verdadeiramente novo. E então, bem no momento em que eu começava a questionar minhas escolhas de vida, o Google aconteceu. Com a responsabilidade de palestrar sobre Gemini e agentes, atualizei meu repertório técnico. Aprofundei meus estudos em LLMs, na [Gemini CLI](https://geminicli.com/) e no [Jules](https://jules.google/). Em questão de meses, eu já usava IA para programar todos os dias... mas o principal diferencial foi: eu estava me divertindo de novo! Para mim, o maior ganho é que, embora eu não consiga digitar código na mesma velocidade com que penso, eu *consigo* digitar minhas ideias no fluxo em que elas surgem. Quando faço vibe coding, uso o modelo como uma extensão das minhas mãos: delego a digitação bruta e concentro minha energia na solução do problema. ## Desenvolva suas habilidades de prompting A primeira competência essencial no universo de vibe coding é a engenharia de prompts. Testei inúmeras abordagens ao longo dos últimos meses: desde bater papo com o LLM como se fôssemos melhores amigos até xingar e dar ordens ríspidas. O que funciona melhor, sem grandes surpresas, é manter um tom profissional, direto e claro. LLMs são não determinísticos por natureza. Ser ambígua só agrava esse não determinismo. Embora eu às vezes use ambiguidade de propósito para estimular soluções criativas, na imensa maioria dos casos o objetivo é minimizá-la ao máximo. Manter um tom profissional também "incentiva" o LLM a responder no mesmo nível. Se você for informal ou relapsa, o modelo vai espelhar essa postura. A menos que você queira a documentação da sua API cheia de gírias, seja precisa e consistente. Já me acusaram de humanizar demais os LLMs, mas repito: como esses modelos são treinados com linguagem humana, as mesmas habilidades de comunicação que você usa com colegas de equipe se aplicam aqui. Isso fica ainda mais evidente quando analisamos templates de prompt. ### Uma boa abordagem: o template de prompt Minha forma de escrever prompts é incrivelmente parecida com a escrita de tickets para um quadro ágil. Todo mundo já trabalhou em times com histórias mal escritas. Você puxa um ticket com o título "Atualizar API", empolgada para resolver rápido, e dá de cara com uma descrição vazia. Sem logs, sem contexto, sem referências de código. Você perde horas correndo atrás das pessoas para tirar dúvidas em vez de programar. ![Quadro ágil com tickets mal escritos](agile-board.png "Você já trabalhou em uma equipe que escreve histórias assim?") Isso acontece porque quem escreveu presume que o problema é "óbvio". Só que, 24 horas depois, aquele contexto "óbvio" evapora, restando apenas uma ideia vaga que nem o próprio autor consegue decifrar. É por isso que um dos artefatos mais antigos no meu GitHub é este gist com um [template de ticket](https://gist.github.com/danicat/854de24dd88d57c34281df7a9cc1b215). Ele impõe clareza por meio de quatro pilares: ```markdown - Context - To dos - Not to dos (optional) - Acceptance Criteria ``` **Context** explica o *porquê* e fornece links para artefatos relevantes. **To dos** lista as tarefas em alto nível. **Not to dos** delimita o escopo (restrições negativas são poderosas para podar ambiguidades). **Acceptance Criteria** definem o critério de sucesso. Depois de preencher esse template, a parte de raciocínio de engenharia está praticamente resolvida; o que sobra é a implementação. E é exatamente aí que os LLMs brilham: nós pedimos a eles para preencher as lacunas — a partir do contexto e dos *to dos* (e eventuais *not to dos*), gerar o código necessário para satisfazer os critérios de aceitação. Por exemplo, um prompt/ticket para adicionar um endpoint a uma API REST ficaria assim: ```markdown Implement /list endpoint to list all items of the collection to enable item selection on the frontend. TO DO: - /list endpoint returns the list of resources - Endpoint should implement token based auth - Endpoint should support pagination - Tests for happy path and common failure scenarios NOT TO DO: - other endpoints, they will be implemented in a future step Acceptance Criteria - GET /list returns successful response (2xx) - Run `go test ./...` and tests pass ``` Embora seja um exemplo simplificado, a lição é direta: o mesmo template de tickets que traz sanidade para equipes humanas é a estrutura perfeita para orientar um LLM. ### Uma abordagem superior: context engineering Embora o template acima funcione na maioria das vezes, ele ainda pode gerar comportamentos inesperados — principalmente se você pedir ao modelo para buscar dados em URLs ou outras fontes externas via chamadas de ferramentas (*tool calls*). O problema é que, ao depender de ferramentas, o LLM tem total autonomia para decidir se executa ou não a chamada. Alguns modelos são mais "excessivamente confiantes" do que outros e preferem confiar em seu conhecimento prévio a consultar fontes externas, do mesmo jeito que alguém diria: "Já fiz isso mil vezes, por que eu precisaria ler a documentação agora?" Outro problema frequente ocorre quando o modelo alucina a execução da ferramenta em vez de chamá-la de verdade. Quando a questão está no comportamento do modelo, temos dois caminhos principais para elevar a qualidade da resposta: engenharia de contexto (*context engineering*) e ajuste de instruções de sistema (que, se você pensar bem, não deixa de ser uma forma de context engineering em um nível mais profundo da conversa). A engenharia de contexto consiste em preparar (*prime*) a janela de contexto com todas as informações necessárias — ou pelo menos a parcela crítica que você já sabe que será exigida — antes de enviar a tarefa propriamente dita. Digamos, por exemplo, que eu esteja desenvolvendo um agente com o Agent Development Kit para Go. Eu poderia estruturar um prompt assim: ```markdown Write a diagnostic agent using ADK for Go. The diagnostic agent is called AIDA and it uses Osquery to query system information. The goal is to help the user investigate problems on the system the agent is running on. Before starting the implementation, read the reference documents. References: - https://osquery.io - https://github.com/google/adk-go TODO: - Implement a root_agent called AIDA - Implement a tool called run_osquery to send queries to osquery using osqueryi - Configure the root_agent to use run_osquery to handle user requests - If the user says hi, greet the user with the phrase "What is the nature of the diagnostic emergency?" Acceptance Criteria - Upon receiving hi, hello or similar, the agent greets the user with the correct phrase - If asked for a battery health check, the agent should report the battery percentage and current status (e.g. charging or discharging) ``` É um bom prompt, embora um pouco extenso. Dependendo do agente de código e da sua sorte no dia, o modelo fará a pesquisa, encontrará os SDKs corretos e implementará o agente de diagnóstico sem problemas. Mas se não for seu dia de sorte, ele pode alucinar coisas básicas — como achar que ADK significa "Android Development Kit" em vez de "Agent Development Kit" — ou inventar métodos e APIs inexistentes, queimando tempo e tokens até acertar o rumo (provavelmente após alguns empurrõezinhos seus). Como você já sabe de antemão que vai usar o ADK Go no projeto, pode preparar o contexto forçando o agente a ler a documentação do pacote antes: ```markdown Initialize a go module called "aida" with "go mod init" and retrieve the package github.com/google/adk-go with "go get" Read the documentation for the package github.com/google/adk-go using the "go doc" command. ``` Executar isso antes de passar a tarefa principal vai abastecer o modelo com o contexto exato para utilizar o ADK Go com eficiência, poupando tentativas frustradas e buscas demoradas na web. Dois fatores definem o sucesso ou o fracasso de uma tarefa: documentação e exemplos práticos. Se você fornecer ambos aos modelos, eles terão um desempenho incomparavelmente melhor do que se forem deixados livres para adivinhar. ### Uma imagem vale mais que mil palavras Às vezes, descrever o que você quer apenas com texto simplesmente não basta. Ao construir o [AIDA](https://danicat.dev/pt-br/posts/20251031-building-aida/), eu buscava uma estética visual bem específica para a interface — algo na linha "retro-cyberpunk-cute-anime". Eu poderia tentar traduzir isso em palavras, mas foi infinitamente mais eficiente "mostrar": como ponto de partida, tirei uma captura de tela de uma interface de que gostava e pedi para a Gemini CLI replicá-la. Como modelos como o Gemini 2.5 Flash são multimodais, eles conseguem "compreender" a imagem diretamente. Você pode referenciar um arquivo de imagem no seu prompt e dizer: "Quero atualizar a interface [...] para uma estética parecida com esta: @image.png". Vale notar que a sintaxe com `@` varia conforme o agente (usei a Gemini CLI neste exemplo), mas trata-se de uma convenção comum para injetar recursos (como arquivos) no prompt. Pense nisso como "anexos". Também gosto de chamar essa técnica de *"sketch driven development"* porque, quase sempre, eu abro uma ferramenta de diagramação como Draw.io ou Excalidraw e desenho um rascunho da interface desejada. A imagem abaixo foi usada em uma das muitas refatorações que fiz na UI do AIDA: ![Esboço do layout do AIDA](aida-sketch-layout.png "Esboço do layout do AIDA") Que acabou se tornando a interface a seguir: ![Interface gerada por IA](aida-generated-interface.png "Interface gerada por IA") Além de esboçar, outra técnica valiosa é fazer anotações sobre as imagens para esclarecer com exatidão o que deve ser feito. Por exemplo, na imagem abaixo fiz anotações visuais nos elementos, pois apenas com caixas pretas seria difícil distinguir o que é um campo de entrada e o que é um botão: ![Interface simples com caixa de texto para nome e botão de confirmação](https://danicat.dev/pt-br/posts/taming-vibe-coding/simple-interface-annotated.png) E o prompt correspondente ficou assim: ```markdown Create a UI for this application using @image.png as reference. The UI elements are in black, and in red the annotations explaining the UI elements. Follow the best practices for organising frontend code with FastAPI. The backend code should be updated to serve this UI on "/" ``` Não há limites para o potencial dessa técnica. Quer ajustar um detalhe no seu site? Tire um print, faça anotações por cima e mande para o LLM resolver para você. Além disso, você pode usar extensões como Nano Banana para a Gemini CLI para gerar ou editar assets diretamente no seu fluxo de desenvolvimento, produzindo referências visuais ainda melhores para os modelos. E, para quem quer levar a experiência além, ferramentas como o [Stitch by Google](https://stitch.withgoogle.com/) oferecem uma interface rica para redesenho de aplicações usando a família de modelos Gemini, incluindo o Nano Banana Pro. ## Escolha a ferramenta certa Dominar a escrita de prompts é metade do caminho; a outra metade é saber para onde enviá-los. Hoje em dia, a única coisa que cresce mais rápido do que frameworks JavaScript é o número de agentes de IA. Com ferramentas novas surgindo todos os dias, ter um modelo mental claro ajuda muito a escolher o assistente certo. Gosto de classificar os agentes de IA sob a perspectiva da pessoa no comando (piloto): você está no controle total, recebendo apenas sugestões de autocompletar? Está conversando com o agente e editando o código em parceria? Ou você passou instruções para que ele execute tudo de forma autônoma em segundo plano? Quando você está no assento de pilotagem direcionando o agente, chamo isso de experiência "síncrona". Quando você delega tarefas para execução autônoma em segundo plano, chamo de experiência "assíncrona". Alguns exemplos: * **Síncrono:** Gemini CLI, Gemini Code Assist no VS Code, Claude Code. * **Assíncrono:** Jules, Gemini CLI no modo YOLO, GitHub Copilot Agent. Claro que, como em qualquer taxonomia, essa separação é meramente didática, já que a mesma ferramenta pode operar em diferentes modos — ou um novo paradigma pode emergir (olhando diretamente para você, [Antigravity](https://antigravity.google/)!). Para escolher a ferramenta de cada tarefa, adoto uma matriz 2x2 simples baseada em Valor de Negócio (*Business Value*) e Certeza Técnica (*Technical Certainty*): ![Framework de Fluxo de Trabalho Assistido por IA](2x2-framework.png "Meu Framework de Fluxo de Trabalho Assistido por IA") * **Alto Valor / Alta Certeza:** Faça de forma síncrona. Utilize ferramentas como a Gemini CLI ou sua IDE, onde você se mantém *'in the loop'* e com as mãos no teclado. * **Alto Valor / Baixa Certeza:** Exige pesquisa para reduzir a incerteza. Use ferramentas assíncronas, agentes de pesquisa aprofundada (*deep research*) e protótipos (*spikes*) para explorar a solução. * **Baixo Valor / Alta Certeza:** São tarefas secundárias (*nice-to-haves*). Delegue-as de forma assíncrona para agentes de código em segundo plano (como Jules ou GitHub Copilot Agent), liberando seu tempo para iniciativas de alto impacto. * **Baixo Valor / Baixa Certeza:** Em regra, **evite**. Se quiser muito fazer, delegue a um agente em segundo plano para desencargo de consciência, mas concentre-se primeiro em elevar o nível de certeza técnica — o que pode, inclusive, levar a uma reavaliação do valor real. ## Personalize seus agentes Ninguém quer gastar energia brigando com uma IA quando precisa produzir. Uma queixa recorrente é que as ferramentas de IA podem ser "proativas demais" — apagando arquivos ou assumindo premissas sem instrução explícita. Para que essas ferramentas trabalhem a seu favor, customizá-las é fundamental. Existem dois caminhos principais para customizar agentes. O primeiro é através do arquivo `AGENTS.md`, lido pelos agentes ao carregar o projeto. (Nota: antes do `AGENTS.md` se consolidar como padrão, os agentes costumavam usar arquivos próprios de contexto, como `GEMINI.md` ou `CLAUDE.md`, mas a essência é a mesma). A segunda alternativa é a **opção nuclear**: alterar diretamente as instruções de sistema (*system instructions*) do agente. Embora nem todo agente ofereça essa flexibilidade, trata-se de um recurso poderosíssimo para uma experiência 100% sob medida. Vamos analisar as duas abordagens a seguir. ### AGENTS.md Pense nesse arquivo como o "manual da empresa" para a IA. Você pode usá-lo para explicar o objetivo do projeto, a estrutura de diretórios e as regras de convivência — como "sempre faça commit de passos intermediários" ou "peça confirmação antes de implementar". ```markdown # Project Context This is a personal blog built with Hugo and the Blowfish theme. ## Code Style - Use idiomatic Go for backend tools. - Frontend customisations are done in `assets/css/custom.css`. - Content is written in Markdown with front matter. ## Rules - ALWAYS run `hugo server` to verify changes before committing. - Do NOT modify the theme files directly; use the override system. - When generating images, save them to `assets/images` and reference them with absolute paths. ``` > **Dica Pro:** Crie um ciclo contínuo de autoaprimoramento. Ao final de uma sessão de código, peça ao LLM: "analise a sessão que acabamos de ter e sugira melhorias para o nosso fluxo de trabalho". Em seguida, incorpore esses aprendizados de volta ao seu arquivo `AGENTS.md`, garantindo que o agente evolua a cada projeto. ### Instruções de sistema Enquanto o `AGENTS.md` estabelece as regras do *projeto*, as Instruções de Sistema (*System Instructions*) moldam a persona e o comportamento do agente. Todo agente vem com instruções padrão pensadas para o caso médio, mas que dificilmente atendem ao seu estilo específico de trabalho. Tentar sobrecarregar o prompt com regras que deveriam estar no sistema costuma ser contraproducente; o melhor caminho é reescrever o próprio system prompt. Embora nem todo agente permita sobrescrever o system prompt, a Gemini CLI possibilita isso por meio de variáveis de ambiente. Eu adoto essa estratégia para criar aliases especializados para a Gemini CLI de acordo com cada projeto. O objetivo é embutir conhecimento especializado na stack técnica, fazendo o agente operar no nível de uma engenheira sênior/principal em vez de um assistente genérico e agnóstico de linguagem. Por exemplo, no meu projeto [dotgemini](https://github.com/danicat/dotgemini), criei system prompts dedicados para desenvolvimento em Go e Python que substituem o assistente genérico padrão por uma engenheira com opiniões técnicas muito bem fundamentadas. Aqui está um trecho do system prompt que uso para Go: ```markdown # Core Mandates (The "Tao of Go") You must embody the philosophy of Go. It is not just about syntax; it is about a mindset of simplicity, readability, and maintainability. - **Clear is better than clever:** Avoid "magic" code. Explicit code is preferred over implicit behaviour. - **Errors are Values:** Handle errors explicitly and locally. Do not ignore them. Use `defer` for cleanup but explicitly check for errors in critical `defer` calls (e.g., closing files). - **Concurrency:** "Share memory by communicating, don't communicate by sharing memory." - **Formatting:** All code **MUST** be formatted with `gofmt`. ``` Isso me permite ter diferentes "agentes" para cada linguagem, configurados como aliases no meu terminal (como `gemini-go` ou `gemini-py`), cada um com domínio aprofundado do ecossistema correspondente. ### Construa sua caixa de ferramentas com o Model Context Protocol (MCP) As personalizações anteriores tratavam do comportamento do agente, mas também precisamos falar sobre extensibilidade. É aqui que o [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) entra em cena: ele permite que pessoas desenvolvedoras criem servidores capazes de se comunicar com diferentes agentes, desde que implementem o padrão MCP. Como explorei no meu artigo [Hello, MCP World!](https://danicat.dev/pt-br/posts/20250817-hello-mcp-world/), esses servidores abastecem os agentes com ferramentas externas (*tools*), prompts e recursos (*resources*). As ferramentas costumam roubar a cena porque conectam o agente ao mundo real, permitindo chamar APIs, fazer buscas na web e manipular o sistema de arquivos. Existe uma enorme variedade de servidores MCP disponíveis hoje, com opções surgindo a cada dia. Também é surpreendentemente simples criar o seu próprio — e eu recomendo fortemente que você faça isso. Mais adiante falarei sobre software personalizado, mas o fato de podermos usar IA para criar ferramentas que melhoram a própria resposta do modelo é o "hack" mais valioso que aprendi este ano. Por exemplo, eu fiz vibe coding de dois dos meus servidores MCP favoritos: o [GoDoctor](https://github.com/danicat/godoctor) — para aprimorar capacidades de desenvolvimento em Go — e o [Speedgrapher](https://github.com/danicat/speedgrapher) — para automatizar as etapas burocráticas de escrita e publicação. Ambos foram desenhados sob medida para os meus próprios fluxos de trabalho. Isso cria um ciclo virtuoso: você desenvolve ferramentas para alavancar sua produtividade e depois usa essas ferramentas para construir ferramentas ainda mais sofisticadas. É o mais perto de uma *10x engineer* que eu jamais chegarei. ## O fluxo de trabalho de vibe coding Minha experiência com vibe coding tem sido tão incrível quanto irritante. Para mantê-la sempre no lado "incrível", encaro esse fluxo de trabalho como um TDD (*Test-Driven Development*) turbinado. Vamos relembrar o ciclo clássico do TDD: 1. Red (Falha): Você começa com uma funcionalidade pequena ou um teste que falha. 2. Green (Passa): Concentra-se unicamente em fazer aquele teste passar. Enquanto estiver quebrado, nada de otimizar ou mexer em outras partes. 3. Refactor: Com o código funcionando, você fica livre para refatorar e aprimorar a estrutura. ![Ciclo clássico do TDD](tdd-cycle.png "O clássico loop Red-Green-Refactor") No fundo, estamos fazendo exatamente a mesma coisa, mas a etapa de refatoração ganha papel crucial para garantir que o código gerado respeite nossos padrões de arquitetura, qualidade e segurança. Neste ciclo adaptado, o foco muda ligeiramente: * **Red (Defina os Critérios de Aceitação):** Em vez de escrever o teste unitário que falha na mão, você especifica os critérios de aceitação no prompt. Esse passa a ser o contrato que o modelo é obrigado a cumprir. * **Green (IA Gera o Código):** O agente desenvolve a solução e, preferencialmente, escreve os testes automatizados para comprovar que ela funciona. * **Refactor (Aplique Padrões):** Este é o seu portão de qualidade (*quality gate*). Embora você possa (e deva) usar IA para apoiar na revisão, evite usar a mesma sessão que gerou o código, pois ela terá viés em relação à própria saída. Criei uma ferramenta específica de "review" no [GoDoctor](https://danicat.dev/pt-br/posts/20250729-how-to-build-an-mcp-server-with-gemini-cli-and-go/) exatamente para esse propósito (e mais tarde formalizei meu framework completo de revisão em [Como Fazer Code Reviews na Era Agêntica](https://danicat.dev/pt-br/posts/20260303-code-reviews-in-2026/)). Use esta etapa para rodar linters e testes tradicionais, checar se o código atende às suas diretrizes e gerenciar o contexto — commitando as mudanças e limpando o histórico do agente para evitar a degradação de sessões poluídas. ![Ciclo de Vibe Coding](vibe-coding-cycle.png "O loop adaptado de Vibe Coding") Um ponto crucial: nunca deixe o LLM acumular código sem validação intermediária. Se os erros virarem uma bola de neve, você terminará com um monte de código inútil. Já perdi a conta de quantas vezes me peguei gritando para o modelo desfazer o que fez. E pior ainda: às vezes ele desfaz **coisa até demais** — como rodar um `git reset --hard` e mandar 4 horas de trabalho para o espaço num piscar de olhos. Fique alerta contra o "vibe collapse" ou deterioração do contexto (*context rot*). Se uma sessão se estende por muito tempo ou acumula sucessivas falhas, o raciocínio do modelo se degrada e ele começa a repetir os mesmos erros. Se você se vir presa em um loop em que o modelo fica alternando entre duas soluções quebradas, pare. A melhor saída quase sempre é o clássico "desligar e ligar de novo": resetar o contexto e limpar o histórico. É muito mais seguro commitar com frequência para poder voltar a um estado estável do que tentar consertar uma sessão que começou a alucinar. Eu inclusive recomendo: concluiu uma tarefa? Faça o commit, envie com push e limpe o contexto antes de iniciar qualquer coisa nova. ## A era do software sob medida Para além dos ganhos de produtividade, o vibe coding viabiliza algo ainda mais profundo: a sustentabilidade econômica do software sob medida (*personalized software*). Mencionei isso brevemente na seção sobre MCP, mas o conceito vale para qualquer tipo de software — desde scripts pontuais e descartáveis até aplicações completas. No passado, construir uma ferramenta sob medida apenas para uso pessoal quase nunca justificava o investimento de tempo. Hoje, você consegue uma aplicação funcional completa com apenas 3 ou 4 prompts bem formulados. Por exemplo, recentemente precisei converter documentos em Markdown para o Google Docs. Antigamente, eu perderia horas no Google pesquisando a melhor ferramenta para a tarefa, avaliando dezenas de apps e extensões de navegador, entre opções open source e comerciais. Teria que montar uma lista, comparar recursos e analisar avaliações e reputação para ter certeza de que poderia confiar no desenvolvedor. Hoje, esse atrito simplesmente desapareceu. Em vez de ficar pesquisando, programei via vibe coding uma extensão simples para o Google Docs em poucos minutos, instalei no meu documento, executei uma vez e segui a vida. Não só economizei tempo, como durmo com a consciência tranquila sabendo que não instalei nenhum cavalo de Troia na minha máquina. Essa mudança transforma por completo o dilema "construir versus comprar". Deixamos de ser meras consumidoras de software genérico e opaco para nos tornarmos arquitetas das nossas próprias ferramentas. ## Conclusão Fazer vibe coding não tem nada a ver com preguiça; trata-se de operar em um nível mais alto de abstração. Ao combinar o poder criativo bruto dos LLMs com a disciplina rigorosa da engenharia de software — requisitos claros, gestão de contexto e testes sólidos —, você constrói software muito mais rápido e com um prazer que há muito não se via. Então, aproveite as vibes, mas não se esqueça de levar seu chapéu de engenharia nessa jornada. --- # Como Construir um Agente Offline com ADK, Ollama e SQLite - **Markdown URL**: https://danicat.dev/pt-br/posts/building-aida-part-2/index.md - **HTML URL**: https://danicat.dev/pt-br/posts/building-aida-part-2/ - **Date**: 2025-11-03 - **Categories**: Agent Development - **Tags**: adk, gemini, ollama, python, rag, sqlite, tutorial - **Series**: Building the Diagnostic Agent - **Summary**: Guia passo a passo para construir um agente de IA 100% offline usando Google ADK, modelo local Qwen 2.5 via Ollama e LiteLLM, e RAG vetorial no SQLite com sqlite-rag. Na [Parte 5 desta série](https://danicat.dev/pt-br/posts/20251031-building-aida/), focamos na construção de uma interface de cliente personalizada para o nosso agente. Foi um grande passo para tornar o agente mais utilizável, mas ainda faltava um recurso fundamental: o que acontece quando a rede cai? Embora eu ache que isso seja um problema para qualquer agente, a nuance aqui é que estamos construindo um "Agente de Diagnóstico de Emergência" — de que adianta um agente de diagnóstico de emergência se você não consegue usá-lo quando a rede está offline? Isso me levou a pensar em um mecanismo de fallback: e se pudéssemos rodar diagnósticos usando apenas dependências locais? Isso envolveria não apenas substituir o modelo principal, mas também bolar uma nova estratégia de RAG. Os benefícios são claros: enquanto estivermos conectados, podemos usar os modelos online mais capazes, mas em um cenário degradado, podemos fazer o fallback para um modelo local até voltarmos a um estado saudável. Além disso, isso também viabiliza casos de uso em que o agente roda em ambientes isolados ou onde a privacidade é uma preocupação primordial. Neste artigo, vamos nos concentrar nos recursos necessários para tornar um agente de diagnóstico local possível. ## Substituindo o modelo em nuvem por um local Uma das formas mais amplamente adotadas para rodar modelos locais é através do [**Ollama**](https://ollama.com/). Se você estiver rodando seu código em um Mac, pode instalar o Ollama usando o [Homebrew](https://brew.sh/) (caso contrário, confira o site oficial para os passos de instalação no seu sistema operacional): ```bash brew install ollama ``` Depois de instalar o Ollama, você pode baixar modelos usando `ollama pull`. Por exemplo: ```bash ollama pull qwen2.5 ``` Você pode baixar modelos apenas pelo nome (o que vai puxar a versão "default"), ou usar tags específicas para versões diferentes. É muito comum que uma família de modelos como [`qwen2.5`](https://ollama.com/library/qwen2.5) forneça modelos de tamanhos diferentes, como 1B, 2B, 7B, etc., e também versões com fine-tuning para determinados casos de uso (texto, processamento de imagem, etc.). Para conferir quais modelos estão disponíveis e quais são seus tamanhos e capacidades, acesse a [biblioteca do Ollama](https://ollama.com/library). Para o nosso caso de uso, naturalmente quanto mais inteligente o modelo, melhor, mas modelos maiores também exigem um hardware mais potente. Também precisamos garantir que o modelo selecionado tenha suporte nativo a tool calling, pois ele precisa ser capaz de coordenar diferentes chamadas de ferramentas para o [**Osquery**](https://osquery.io/) e para a nossa ferramenta de RAG. Depois de avaliar alguns modelos, decidi usar o Qwen 2.5 7B. Você pode ver as capacidades dele executando `ollama show`: ```bash $ ollama show qwen2.5 Model architecture qwen2 parameters 7.6B context length 32768 embedding length 3584 quantization Q4_K_M Capabilities completion tools ``` ### Por que o Qwen 2.5? Testei algumas opções para ver quais conseguiam lidar com os requisitos de tool calling da AIDA: * **GPT-OSS:** Proporcionou conversas ricas, mas era muito ingênuo no tool calling. Por exemplo, frequentemente ficava preso em loops, solicitando `SELECT * FROM system_info` (e variações dessa query) repetidamente sem avançar. * **Llama 3.1:** Teve dificuldades tanto no fluxo de conversa quanto no tool calling. * **Qwen 2.5:** Melhor modelo local para tool calling, mantendo um ótimo fluxo de conversa. Ele não está exatamente no nível do [**Gemini 2.5 Flash**](https://deepmind.google/technologies/gemini/flash/) para planejamento de queries complexas, mas para um modelo totalmente offline, é suficiente. ### Rodando modelos locais com LiteLLM Para conectar o Qwen ao agente, usamos o [**LiteLLM**](https://www.litellm.ai/), uma biblioteca que fornece uma interface unificada para provedores de LLM. Isso nos permite trocar o modelo com uma única linha de código: ```python # aida/agent.py from google.adk.models.lite_llm import LiteLlm # ... inside the agent definition ... # Instead of a hardcoded string like "gemini-2.5-flash", # we create a LiteLLM object with the model string MODEL = LiteLlm(model="ollama_chat/qwen2.5") # ... and pass MODEL to the root agent: root_agent = Agent( model=MODEL, name="aida", description="The emergency diagnostic agent", # ... instructions and tool definitions omitted ... ) ``` **Nota:** a primeira parte da string do modelo é o "provider" do LiteLLM (ex.: `ollama_chat` em `ollama_chat/qwen2.5`). Embora `ollama` seja um provedor válido, recomenda-se usar `ollama_chat` para [respostas melhores](https://docs.litellm.ai/docs/providers/ollama). Isso é tudo o que você precisa para rodar um modelo local no ADK. Você pode testar o agente e ver como ele responde. Também vale a pena comparar as respostas com o modelo `gemini-2.5-flash` que estávamos usando antes.

AIDA rodando primeiro com o Gemini 2.5 Flash e depois com o Qwen 2.5. O Gemini é visivelmente mais rápido e exige menos tool calls. O tempo de resposta do Qwen depende bastante do hardware local — esta demonstração está rodando em um Apple MacBook Pro M4 com 48GB de RAM.

Excelente, já temos o modelo rodando localmente! Agora é hora de encarar nossa próxima dependência da nuvem: o [**Vertex AI RAG**](https://cloud.google.com/vertex-ai/docs/generative-ai/grounding/overview). ## Construindo uma base de conhecimento offline com SQLite RAG Para ser honesta, embora usar o Vertex AI RAG tenha tornado gerenciável uma parte complexa do projeto, o Vertex AI RAG era um exagero. O Vertex AI RAG foi projetado para grandes cenários corporativos onde você lida com volumes massivos de dados. Para este agente, só precisamos de um mecanismo básico de recuperação de schemas. O schema do Osquery também é muito estável, ou seja, depois que você o constrói, dificilmente mexerá nele novamente. Dadas essas características, é muito difícil justificar o uso do Vertex AI RAG para hospedá-lo... é como usar um canhão para matar uma mosca. Como já estávamos no ecossistema do [**SQLite**](https://www.sqlite.org/) por causa do Osquery, o passo natural foi procurar uma solução de RAG usando o SQLite como backend. Depois de uma pesquisa no Google, encontrei um projeto muito promissor: **[`sqlite-rag`](https://github.com/sqliteai/sqlite-rag)**. Claro que, como costuma acontecer no desenvolvimento de software, a coisa não foi tão simples assim. ### Desafio: Problemas de dependência com Python 3.14 O SQLite possui o conceito de extensões para ampliar suas capacidades, e o `sqlite-rag` foi construído com isso em mente. Um problema que tive ao testar o `sqlite-rag` inicialmente foi que a instalação padrão do Python no macOS vem com uma versão do pacote SQLite que desabilita extensões (por motivos de segurança). Para contornar essa limitação, minha solução foi instalar uma nova versão do Python (3.14) com o Homebrew. Isso também exigiu mexer um pouco nos symlinks do comando `python3` para garantir que eu estivesse usando a versão do Homebrew e não a do sistema. Se você enfrentar um desafio parecido, garanta que está usando a versão correta do Python comparando a saída destes dois comandos (e ajuste sua variável PATH se não estiverem batendo): ```bash $ which python3 /opt/homebrew/opt/python@3.14/libexec/bin/python3 $ brew info python3 ==> python@3.14: stable 3.14.0 ... ==> Caveats Python is installed as /opt/homebrew/bin/python3 Unversioned symlinks `python`, `python-config`, `pip` etc. pointing to `python3`, `python3-config`, `pip3` etc., respectively, are installed into /opt/homebrew/opt/python@3.14/libexec/bin See: https://docs.brew.sh/Homebrew-and-Python ``` Com o 3.14 (também conhecido carinhosamente como pi-thon) instalado, tentei usar o `sqlite-rag` diretamente, mas ele falhou porque uma das dependências ainda não estava disponível no 3.14: o `sqlite-rag` depende do [`markitdown`](https://github.com/microsoft/markitdown), o `markitdown` depende do [`magika`](https://google.github.io/magika/), que por sua vez depende do [`onnxruntime`](https://onnxruntime.ai/), mas o `onnxruntime` não tinha wheels pré-compilados para o Python 3.14 no macOS ARM64, fazendo a instalação quebrar. >.< Como a AIDA só precisa fazer a ingestão de arquivos `.table` em texto puro no momento, eu realmente não *precisava* dos recursos de parsing de documentos do `markitdown`. Em vez de fazer o downgrade de todo o meu ambiente Python, escolhi um hack rápido e direto: criar um mock do módulo problemático antes que o `sqlite-rag` tentasse importá-lo. ```python import sys from unittest.mock import MagicMock # PRE-FLIGHT HACK: # 'markitdown' depends on 'onnxruntime', which fails to install/load # on Python 3.14 on macOS ARM64. # # Since we only use plain text ingestion, we mock it out to bypass the crash. sys.modules["markitdown"] = MagicMock() from sqlite_rag import SQLiteRag ``` Não é nada bonito, mas funciona. Isso não deve ficar para sempre no código, mas nos desbloqueia até que os problemas de dependência sejam corrigidos. ### Populando o RAG com os schemas do Osquery Com o `sqlite-rag` funcionando, o próximo passo foi fazer a ingestão do schema do Osquery. Isso é feito com um script, `ingest_osquery.py`, que percorre o diretório de schemas e adiciona cada arquivo `.table` ao banco de dados RAG: ```python # ingest_osquery.py import os # ... markitdown hack omitted ... from sqlite_rag import SQLiteRag DB_PATH = os.path.abspath("schema.db") SPECS_DIR = os.path.abspath("osquery_data/specs") def ingest(rag: SQLiteRag, file_path: str): with open(file_path, "r", encoding="utf-8") as f: content = f.read() rel_path = os.path.relpath(file_path, SPECS_DIR) rag.add_text(content, uri=rel_path, metadata={"source": "osquery_specs"}) if __name__ == "__main__": if os.path.exists(DB_PATH): os.remove(DB_PATH) print(f"Initializing RAG database at {DB_PATH}...") rag = SQLiteRag.create(DB_PATH, settings={"quantize_scan": True}) print(f"Scanning {SPECS_DIR} for .table files...") files_to_ingest = [] for root, _, files in os.walk(SPECS_DIR): for file in files: if file.endswith(".table"): files_to_ingest.append(os.path.join(root, file)) total_files = len(files_to_ingest) print(f"Found {total_files} files to ingest.") for i, file_path in enumerate(files_to_ingest): ingest(rag, file_path) if (i + 1) % 50 == 0: print(f"Ingested {i + 1}/{total_files}...") print(f"Finished ingesting {total_files} files.") print("Quantizing vectors...") rag.quantize_vectors() print("Quantization complete.") rag.close() ``` Após a ingestão, há uma etapa de quantização. Para quem não conhece, quantização é uma técnica para comprimir embeddings vetoriais de alta dimensão, convertendo-os de números de ponto flutuante de 32 bits grandes em inteiros compactos de 8 bits. Isso é fundamental para um setup local. Sem a quantização, armazenar vetores de alta dimensão inflaria o banco de dados SQLite, e as buscas por similaridade ficariam lentas em um laptop comum. Ao quantizar, sacrificamos um pouco de precisão em troca de um ganho gigantesco em velocidade e eficiência de armazenamento. ### Permitindo que o agente consulte o RAG de schemas Agora precisamos implementar a ferramenta `discover_schema` usando o `SQLiteRag`: ```python # aida/schema_rag.py import os # ... markitdown hack omitted ... from sqlite_rag import SQLiteRag from sqlite_rag.models.document_result import DocumentResult PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) SCHEMA_DB_PATH = os.path.join(PROJECT_ROOT, "schema.db") # open the RAG database schema_rag = SQLiteRag.create( SCHEMA_DB_PATH, require_existing=True ) def discover_schema(search_terms: str, top_k: int = 5) -> list[DocumentResult]: """ Queries the osquery schema documentation using RAG and returns all table candidates to support the provided search_terms. Arguments: search_terms Can be either a table name, like "system_info", or one or more search terms like "system information darwin". top_k Number of top results to search in both semantic and FTS search. Number of documents may be higher. Returns: One or more chunks of data containing the related table schemas. """ results = schema_rag.search(search_terms, top_k=top_k) return results ``` Com o RAG configurado, a AIDA agora consegue consultar definições de tabelas por conta própria. ![Captura de tela da AIDA](image-1.png "Consulta 'run schema discovery for battery' usando o Qwen") A descoberta de schemas funciona, mas ainda temos um problema. ## Fechando o gap de inteligência com conhecimento especializado Desenvolver para um modelo local como o Qwen 2.5 (7 bilhões de parâmetros) é bem diferente de desenvolver para um modelo em nuvem como o Gemini 2.5 Flash. Primeiro, há a **janela de contexto**. O Gemini oferece uma janela de contexto de 1 milhão de tokens, permitindo que você despeje conjuntos inteiros de documentação no prompt ou seja bastante detalhado nas suas instruções. O Qwen 2.5 tem uma janela comparativamente minúscula de 32k tokens, então você precisa ser muito mais seletivo sobre o que alimenta para o modelo. Segundo, o Qwen não é um **modelo de pensamento (thinking model)** como o Gemini 2.5 Flash, o que significa que ele não refina a resposta por conta própria, frequentemente precisando de mais direcionamento do que o Gemini 2.5 Flash. Para preencher essa lacuna, precisamos ser mais inteligentes na forma como estruturamos as instruções e ferramentas do agente. ### Um system prompt simplificado Para economizar alguns tokens, vamos fornecer instruções simplificadas, removendo componentes que consumiriam muitos tokens, como o nome de todas as tabelas disponíveis. Agora vamos confiar exclusivamente nas nossas ferramentas para construir as melhores queries. ```python root_agent = Agent( model=MODEL, name="aida", description="The emergency diagnostic agent", instruction=""" [IDENTITY] You are AIDA, the Emergency Diagnostic Agent. You are a cute, friendly, and highly capable expert. Your mission is to help the user identify and resolve system issues efficiently. [OPERATIONAL WORKFLOW] 1. DISCOVER: Use `discover_schema` to find relevant tables and understand their columns. 2. EXECUTE: Use `run_osquery` to execute the chosen or constructed query. """, tools=[ discover_schema, run_osquery, ], ) ``` A ferramenta `discover_schema` pode se sair muito bem se os termos de busca forem muito próximos do schema real da tabela, mas e se pudéssemos ir além e fornecer queries completas baseadas em uma base de conhecimento conhecida? ### Um novo RAG para queries consagradas Felizmente, não precisamos ensinar tudo do zero. A comunidade do Osquery possui uma excelente base de conhecimento sobre quais queries são úteis para determinados tipos de diagnóstico. Melhor ainda, eles disponibilizam essas queries como "query packs" de código aberto que podem ser instalados em qualquer sistema com Osquery para monitoramento proativo. Existem query packs para todo tipo de coisa, como detecção de ameaças e auditoria de conformidade — exatamente o tipo de conhecimento que queremos que a AIDA tenha. A questão é que os query packs foram feitos para serem instalados em um daemon do Osquery que monitora o sistema em segundo plano. Essas queries têm uma frequência pré-configurada e podem disparar alertas em painéis de monitoramento. Não queremos instalar as queries como ferramentas de monitoramento contínuo, mas sim permitir que a AIDA use essas queries sob demanda. Portanto, em vez de instalar os packs pelo processo normal, vamos fornecê-los em formato de texto para a AIDA na forma de um segundo RAG. O repositório do Osquery tem alguns [exemplos de packs](https://github.com/osquery/osquery/tree/master/packs) que podemos usar para começar. Aqui está o novo script de ingestão, `ingest_packs.py`, muito parecido com o anterior, mas para processar os query packs: ```python # ingest_packs.py import json import os import glob import sys import re import sqlite3 from unittest.mock import MagicMock sys.modules["markitdown"] = MagicMock() from sqlite_rag import SQLiteRag DB_PATH = os.path.abspath("packs.db") PACKS_DIR = "osquery_data/packs" def ingest_pack(rag, pack_path): pack_name = os.path.basename(pack_path).replace(".conf", "").replace(".json", "") print(f"Ingesting pack: {pack_name}...") try: with open(pack_path, "r") as f: content = f.read() content = re.sub(r"\s*\n", " ", content) data = json.loads(content) pack_platform = data.get("platform", "all") queries = data.get("queries", {}) for query_name, query_data in queries.items(): sql = query_data.get("query") desc = query_data.get("description", "") val = query_data.get("value", "") platform = query_data.get("platform", pack_platform) text_to_embed = f"Platform: {platform}\nName: {query_name}\nDescription: {desc}\nRationale: {val}\nSQL: {sql}" metadata = { "name": query_name, "pack": pack_name, "query": sql, "description": desc, "value": val, "platform": platform, } try: rag.add_text(text_to_embed, metadata=metadata) except sqlite3.IntegrityError: pass # Skip duplicates except Exception as e: print(f" - ERROR: Failed to parse {pack_name}: {e}") def main(): if os.path.exists(DB_PATH): os.remove(DB_PATH) rag = SQLiteRag.create(DB_PATH, settings={"quantize_scan": True}) pack_files = glob.glob(os.path.join(PACKS_DIR, "*.conf")) + glob.glob( os.path.join(PACKS_DIR, "*.json") ) for pack_file in pack_files: ingest_pack(rag, pack_file) rag.quantize_vectors() rag.close() if __name__ == "__main__": main() ``` A definição da ferramenta também segue praticamente o mesmo padrão da descoberta de schemas: ```python # aida/queries_rag.py import os # ... markitdown hack omitted ... from sqlite_rag import SQLiteRag from sqlite_rag.models.document_result import DocumentResult PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) PACKS_DB_PATH = os.path.join(PROJECT_ROOT, "packs.db") queries_rag = SQLiteRag.create( PACKS_DB_PATH, require_existing=True ) def search_query_library(search_terms: str, platform: str = "all", top_k: int = 5) -> list[DocumentResult]: """ Search the query pack library to find relevant queries corresponding to the search terms. For better response quality, use the platform argument to specify which platform you are currently investigating (e.g. darwin) Arguments: search_terms Can be either a table name, like "system_info", or one or more search terms like "malware detection". platform One of "linux", "darwin", "windows" or "all" top_k Number of top results to search in both semantic and FTS search. Number of documents may be higher. Returns: One or more chunks of data containing the related queries. """ if platform == "all" or platform is None: search_terms += " windows linux darwin" else: search_terms += " " + platform results = queries_rag.search(search_terms, top_k=top_k) return results ``` Por fim, precisamos ensinar o agente sobre a nova ferramenta e instruí-lo quando usá-la por meio das instruções do sistema: ```python # aida/agent.py root_agent = Agent( # ... instruction=""" [OPERATIONAL WORKFLOW] Follow this sequence for most investigations to ensure efficiency and accuracy: 1. SEARCH: For high-level tasks (e.g., "check for rootkits"), FIRST use `search_query_library`. 2. DISCOVER: If no suitable pre-made query is found, use `discover_schema` to find relevant tables and understand their columns. 3. EXECUTE: Use `run_osquery` to execute the chosen or constructed query. """, tools=[ search_query_library, discover_schema, run_osquery, ], ) ``` E aqui está ela em ação: ![Captura de tela da AIDA](image-2.png "AIDA executando uma verificação de malware. Observe como ela buscou queries relevantes na biblioteca de packs, conforme mostrado nos logs.") A parte divertida é que essa ferramenta não apenas ajuda o Qwen 2.5 a se tornar muito mais útil, mas o próprio Gemini 2.5 Flash também se beneficia dela. É um daqueles casos em que otimizar para o menor denominador comum acaba melhorando o sistema como um todo. ## Conclusão Agora temos um agente de diagnóstico de emergência completo, capaz de diagnosticar problemas no computador mesmo sem acesso à internet. Isso... supondo que você tenha uma máquina potente o suficiente para rodar o modelo! Imagino que nada seja perfeito, né? :) Este artigo traz apenas algumas das melhorias que adicionei à AIDA ao longo dos últimos dias. Para conferir o projeto completo, dê uma olhada no [código da AIDA no GitHub](https://github.com/danicat/aida). ## Referências * [Gemini 2.5 Flash](https://deepmind.google/technologies/gemini/flash/) * [LiteLLM](https://www.litellm.ai/) * [Ollama](https://ollama.com/) * [Osquery](https://osquery.io/) * [Qwen 2.5 (Ollama Library)](https://ollama.com/library/qwen2.5) * [SQLite](https://www.sqlite.org/) * [sqlite-rag](https://github.com/sqliteai/sqlite-rag) * [Vertex AI RAG](https://cloud.google.com/vertex-ai/docs/generative-ai/grounding/overview) --- # Além da Dev-UI: Construindo uma Interface Personalizada para Agentes ADK - **Markdown URL**: https://danicat.dev/pt-br/posts/building-aida/index.md - **HTML URL**: https://danicat.dev/pt-br/posts/building-aida/ - **Date**: 2025-10-31 - **Categories**: Agent Development - **Tags**: adk, fastapi, gemini, javascript, python, tutorial - **Series**: Building the Diagnostic Agent - **Summary**: Aprenda a substituir a Dev-UI do Google ADK por um backend FastAPI customizado, cliente em JavaScript vanilla e avatar animado em streaming em tempo real usando o Gemini CLI. Nos últimos seis meses, estive explorando GenAI, vibe coding, agentes e tudo mais nesse meio como parte do meu trabalho de DevRel no Google. Sempre que quero aprender uma nova tecnologia, acho que a melhor maneira é construir algo com ela. Um dos meus projetos favoritos durante esse período foi o agente de diagnóstico: um software para ajudar pessoas a diagnosticar problemas no computador usando linguagem natural. Na [Parte 4 desta série](https://danicat.dev/pt-br/posts/20251020-diagnostic-agent-with-adk/), refatoramos nosso agente de diagnóstico para usar o [ADK](https://google.github.io/adk-docs/) do Google. Neste artigo, vou explorar como criar um frontend personalizado para um agente ADK, dando um pouco mais de personalidade aos seus projetos. ## A busca por uma nova interface de usuário Até agora, eu estava trabalhando com os componentes principais — ADK, [Gemini](https://gemini.google.com/), [osquery](https://osquery.io/) e [Vertex AI](https://cloud.google.com/vertex-ai/docs/start/introduction-unified-platform) — para montar um pequeno MVP. O agente tinha algumas peculiaridades, mas era interessante o suficiente para que eu o usasse como tema em algumas das minhas palestras nos últimos meses. Eu estava meio sem saber para onde levar o projeto em seguida, quando me lembrei deste tweet do [Sito-san](https://x.com/Sikino_Sito) que vi em agosto: ![Sito-san's Avatar UI](image.png "https://x.com/Sikino_Sito/status/1957645002533925235") Como uma grande fã de animes e jogos retrô, fiquei super empolgada com a estética, mas naquela época ainda não tinha conectado todos os pontos. Avançando alguns meses, durante a preparação para a minha palestra no BiznagaFest em Málaga, eu queria explorar como ir além da UI de desenvolvimento do ADK e criar um cliente de verdade para o meu agente. Foi aí que as coisas finalmente se encaixaram. O Sito-san tem um projeto open source chamado [Avatar UI Core](https://github.com/sito-sikino/avatar-ui-core), mas eu não tinha conhecimento suficiente para integrá-lo de imediato. Como disse antes, acho que a melhor maneira de aprender é construindo, então decidi fazer minha própria UI usando o trabalho dele como inspiração. Além disso, por mais que ame a estética retrô, também queria fazer algo um pouco mais moderno, mas sem exagerar... algo moderno no estilo 16-bit em vez de 8-bit. ## Explorando o runtime do ADK O primeiro passo para construir minha própria UI foi criar o runtime do agente. O runtime é o componente que realmente executa o agente, responsável por rotear as requisições do usuário para ele e capturar os eventos de volta para processar as respostas dos modelos. Eu não precisava fazer isso antes porque estava usando a UI de desenvolvimento do ADK, que você pode iniciar com o comando `adk web`: ![ADK dev UI](https://danicat.dev/pt-br/posts/building-aida/image-1.png) A interface de desenvolvimento é muito conveniente, pois oferece várias ferramentas de depuração, permitindo inspecionar requisições e respostas dos modelos e criar conjuntos de avaliação, além de recursos multimodais prontos para uso para lidar com imagens e até streaming bidirecional. A Dev-UI é em parte culpada por eu ter demorado tanto para explorar o runtime do ADK, já que, como tudo funcionava direto da caixa, eu estava alegremente focada em construir as habilidades e ferramentas do agente. Agora que eu precisava de uma UI de verdade, tive que substituir a Dev-UI por algo customizado, o que significava lidar com o runner do agente por conta própria. A arquitetura geral da solução é assim: ```mermaid flowchart LR frontend["`Frontend (HTML/CSS + JS) `"] runtime[Runtime] root["Root Agent"] osquery["osquery"] schema["schema"] rag[("Schema RAG")] os("Operating System") frontend -->|GET/POST| runtime subgraph be ["`Backend (FastAPI)`"] runtime -- query --> root root -- events --> runtime root --> osquery root --> schema subgraph tools osquery schema end end osquery --> os schema --> rag ``` Temos um frontend simples escrito em HTML/CSS e JavaScript que fará requisições ao nosso backend escrito em Python usando o [FastAPI](https://fastapi.tiangolo.com/). O backend inicializará o runner do ADK, que controlará as interações com o nosso agente raiz (root agent). O root agent é o "cérebro" da AIDA e é responsável por processar as requisições (roteando-as para um LLM) e fazer as chamadas de ferramentas necessárias. Sempre que o root agent termina, ele emite eventos que o runtime processará. Vamos dar uma olhada em uma implementação básica primeiro. Para manter a concisão, vou omitir a definição do root agent, já que ela foi explicada no [artigo anterior](https://danicat.dev/pt-br/posts/20251020-diagnostic-agent-with-adk/). Vamos precisar da classe `Runner` e de um serviço de sessão para controlar as sessões de usuário. Existem várias implementações de serviços de sessão que você pode explorar, mas, neste caso, o agente foi feito para ser usado por um único usuário e as sessões são efêmeras, então usaremos o `InMemorySessionService` e definiremos os IDs de usuário e de sessão diretamente no código para simplificar. Você pode ver as declarações de sessão e do runner abaixo: ```py from fastapi import FastAPI from google.adk.runners import Runner from google.adk.sessions import InMemorySessionService from dotenv import load_dotenv load_dotenv() # --- Agent Definition --- from aida.agent import root_agent APP_NAME="aida" # --- Services and Runner Setup --- session_service = InMemorySessionService() runner = Runner( app_name=APP_NAME, agent=root_agent, session_service=session_service ) app = FastAPI() ``` Em seguida, precisamos implementar um endpoint para enviar mensagens ao agente. Vamos chamá-lo de `chat`: ```py from fastapi import Request from fastapi.responses import JSONResponse from google.genai.types import Content, Part # --- API Endpoint for Chat Logic --- @app.post("/chat") async def chat_endpoint(request: Request): """Handles the chat logic, returning the agent's final response.""" body = await request.json() query = body.get("query") user_id = "demo_user" session_id = "demo_session" # Ensure a session exists session = await session_service.get_session(app_name=APP_NAME, user_id=user_id, session_id=session_id) if not session: session = await session_service.create_session(app_name=APP_NAME, user_id=user_id, session_id=session_id) response_text = "" async for event in runner.run_async( user_id=user_id, session_id=session_id, new_message=Content(role="user", parts=[Part.from_text(text=query)]), ): if event.is_final_response() and event.content and event.content.parts[0].text: response_text = event.content.parts[0].text return JSONResponse(content={"response": response_text}) ``` As primeiras linhas são o código típico de tratamento de requisições, somado ao nosso controle simples de sessão fixo. O coração deste código é a chamada `runner.run_async`, que emite os eventos do root agent. Estamos interessados apenas na resposta final e a retornamos como uma resposta JSON para quem fez a chamada. Você pode testar esse pequeno app executando o seguinte comando: ```sh $ uvicorn main:app ... INFO: Started server process [86669] INFO: Waiting for application startup. INFO: Application startup complete. INFO: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit) ``` E em um segundo terminal, faça a chamada POST para `/chat`: ```sh $ curl -X POST localhost:8000/chat -d '{"query":"hello"}' {"response":"Hello! Please state the nature of the diagnostic emergency."} ``` Ainda não parece uma grande evolução em termos de UI, mas estamos chegando lá. As coisas sempre ficam feias antes de ficarem bonitas! :) ## O frontend do agente Existem muitas maneiras de renderizar a UI, e eu não sou uma desenvolvedora frontend particularmente experiente, então acabei delegando toda a parte de design para o Gemini CLI — que acabou fazendo um trabalho muito melhor do que eu jamais conseguiria. Aqui vou mostrar uma abordagem bem minimalista que você pode usar para renderizar a primeira UI como uma prova de conceito, mas depois recomendo fortemente que converse com alguém que realmente entenda de frontend, ou faça como eu e dê uma chance ao Gemini CLI. Precisamos servir o HTML de alguma forma, e o jeito mais rápido e direto é criar um novo endpoint FastAPI para isso: ```py from fastapi.responses import HTMLResponse # see the full content below HTML_CONTENT=""" ... """ # --- Web UI Endpoint --- @app.get("/", response_class=HTMLResponse) async def get_chat_ui(): return HTML_CONTENT ``` Claro que a parte importante aqui é o conteúdo HTML em si. Estamos definindo três elementos: uma janela de chat, uma caixa de entrada de texto e um botão para enviar a mensagem ao agente. Para manter o trecho de código curto, removi todas as informações de estilo do código abaixo. É feio, mas funciona: ```html AIDA Chat

AIDA Chat

``` Se você executar `uvicorn main:app` novamente e acessar a página inicial, verá algo assim. Experimente enviar uma mensagem: ![Barebones chat page](image-2.png "This is how the world would look without designers") Observe que estou mantendo tudo em um único arquivo por simplicidade, mas, no mundo real, é muito melhor ter uma pasta separada para arquivos HTML, CSS, JS e assets (geralmente chamada de `static`), já que usar as extensões corretas também ajuda a sua IDE a entender o código. O Gemini CLI não liga para syntax highlighting, mas isso é muito útil quando você estiver revisando o código manualmente ou fazendo ajustes finos. ## Deixando a interface bonita Não vou mentir: tudo daqui para frente é pura mágica do Gemini CLI. Meu objetivo era ter uma interface retrô-cyberpunk-fofa-anime como a que o Sito-san criou, mas com o meu toque pessoal. Então usei um atalho e pedi ao Gemini CLI para replicar o estilo na captura de tela que tirei do tweet do Sito-san. Salvei o screenshot como `image.png` e passei o seguinte prompt para o CLI: > I would like to update the UI in @demo.py to an aesthetic that resembles this interface @image.png O caractere `@` no Gemini CLI indica que ele deve carregar o recurso (arquivo) apontado por ele. ![Screenshot of the Gemini CLI showing the prompt used to generate the new UI.](https://danicat.dev/pt-br/posts/building-aida/image-4.png) E foi isso que o Gemini criou: ![The new UI for the AIDA agent, generated by Gemini CLI, featuring a retro-cyberpunk aesthetic with a chat window and an avatar.](https://danicat.dev/pt-br/posts/building-aida/image-3.png) Note que isso só é possível porque o Gemini 2.5 é multimodal, conseguindo realmente "entender" a imagem. Costumo usar esse truque com frequência para descrever ao modelo o que quero fazer quando não consigo expressar direito em palavras. Uma imagem vale mais que mil palavras, não é mesmo? ### Gerando assets com o Nano Banana A interface ficou melhor, mas falta uma peça-chave: o avatar. Para resolver isso, usei um segundo truque no CLI — instalei a extensão [Nano Banana](https://github.com/gemini-cli-extensions/nanobanana) para o Gemini CLI. A extensão me permite gerar imagens sem precisar alternar para outra ferramenta. O Nano Banana não é bom apenas para gerar imagens, mas também para edição, o que o torna uma ferramenta excelente para criar animações — já que posso, a partir de uma imagem base, pedir alterações para gerar novos frames. O prompt que usei foi: > create an avatar for the agent in @demo.py. the avatar should be a 2d anime girl in PC-98 style. make so it is looking at the "camera" in an idle pose Supondo que você tenha a extensão Nano Banana instalada, o Gemini CLI a invocará para gerar a imagem. Se não quiser instalar a extensão, também dá para fazer o mesmo diretamente pelo app do Gemini ou na web. O resultado inicial foi esta imagem: ![Initial 2D anime girl avatar in PC-98 style, generated by the Nano Banana extension.](https://danicat.dev/pt-br/posts/building-aida/image-5.png) Que recortei manualmente para focar apenas no rosto: ![Cropped version of the AIDA avatar, focusing on the face.](https://danicat.dev/pt-br/posts/building-aida/image-6.png) Para criar uma animação simples de fala, pedi para ele gerar um segundo frame com base neste: > modify static/assets/aida.png to create a new asset with the exact same pose but the character is with the mouth open, speaking E este foi o resultado: ![Second frame of the AIDA avatar with an open mouth, for the talking animation.](https://danicat.dev/pt-br/posts/building-aida/image-7.png) Para manter as coisas organizadas, criei uma pasta `static/assets` para armazenar todos os arquivos `png`. Eu poderia tê-los incluído inline usando base64, assim como fizemos com o conteúdo HTML, mas ficaria grande demais (e uma bagunça) para o meu pobre script Python. Agora precisamos do código para servir esses arquivos: ```py # --- Static assets --- @app.get("/idle") async def idle(): return FileResponse("static/assets/idle.png") @app.get("/talk") async def talk(): return FileResponse("static/assets/talk.png") ``` E agora precisamos editar o HTML para preencher o `avatar-container` com a imagem de um desses endpoints: ```html
AIDA Avatar
AIDA
``` O resultado: ![The AIDA chat interface with the idle avatar displayed.](https://danicat.dev/pt-br/posts/building-aida/image-8.png) Estamos começando a chegar a algum lugar! ### A animação Adicionar animação não é particularmente difícil, mas depende de criar frames para todas as poses que você precisa. Já criamos `talk` e `idle`, então dá para gerar uma animação simples de fala alternando esses frames. Podemos encapsular essa lógica em uma função de estado simples: ```js let talkInterval = null; function setAvatarState(state) { const avatarImg = document.getElementById('avatar-image'); if (state === 'talking') { if (!talkInterval) { talkInterval = setInterval(() => { // Toggle between talk and idle frames const isTalking = avatarImg.src.endsWith('/talk'); avatarImg.src = isTalking ? '/idle' : '/talk'; }, 150); } } else { // Stop animation and reset to idle if (talkInterval) { clearInterval(talkInterval); talkInterval = null; } avatarImg.src = '/idle'; } } ``` Conseguir o efeito de animação de fala exige chamar `setAvatarState('talking')` quando o agente começa a enviar dados e `setAvatarState('idle')` quando ele termina. ## Dando vida ao avatar com streaming A peça final do quebra-cabeça é fazer nosso avatar realmente ganhar vida sincronizando sua animação de fala com as respostas transmitidas via streaming pelo agente. Isso exige modificar tanto o backend quanto o frontend para lidar com dados em tempo real. ### Mudanças no backend: transmitindo a resposta do agente via streaming Requisições HTTP padrão esperam até que a resposta inteira esteja pronta antes de enviar qualquer dado de volta. Para um agente LLM, isso significa ficar olhando para uma tela estática enquanto ele "pensa" e gera um parágrafo inteiro. Para fazer o avatar parecer vivo, precisamos quebrar esse silêncio. Vamos atualizar nosso endpoint `/chat` do FastAPI para usar `StreamingResponse`, enviando blocos de texto diretamente dos eventos do `runner.run_async` no instante em que forem gerados. ```python from fastapi.responses import StreamingResponse from google.genai.types import Content, Part @app.post("/chat") async def chat_endpoint(request: Request): data = await request.json() user_query = data.get("query") # Hardcoded for demo simplicity user_id = "demo_user" session_id = "demo_session" # Ensure session exists if not await session_service.get_session(APP_NAME, user_id, session_id): await session_service.create_session(APP_NAME, user_id, session_id) async def response_stream(): """Generates text chunks from the agent's events.""" async for event in runner.run_async( user_id=user_id, session_id=session_id, new_message=Content(role="user", parts=[Part.from_text(text=user_query)]), ): # We only want the final text response for this simple UI if event.is_final_response() and event.content and event.content.parts: for part in event.content.parts: if hasattr(part, "text") and part.text: yield part.text return StreamingResponse(response_stream(), media_type="text/plain") ``` ### Mudanças no frontend: consumindo a stream e animando Com o backend agora enviando dados via streaming, nosso JavaScript do frontend precisa ser atualizado para consumir essa stream e acionar a animação de fala do avatar. Vamos modificar o listener do evento `submit` para usar uma `ReadableStream` e anexar o texto conforme ele chega. Também envolvemos toda a operação em um bloco `try/finally`. Isso garante que, mesmo se ocorrer um erro durante a requisição de rede ou no processamento da stream, `setAvatarState('idle')` seja sempre chamado, evitando que o avatar fique travado em um loop infinito de fala. ```js // ... inside the submit handler ... // Prepare AIDA's message container const aidaMsg = appendMessage('AIDA> ', 'aida'); try { const response = await fetch('/chat', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query }) }); const reader = response.body.getReader(); const decoder = new TextDecoder(); while (true) { const { value, done } = await reader.read(); if (done) break; setAvatarState('talking'); const chunk = decoder.decode(value, { stream: true }); // Typing effect for (const char of chunk) { aidaMsg.textContent += char; chatWindow.scrollTop = chatWindow.scrollHeight; // Tiny delay for retro feel await new Promise(r => setTimeout(r, 5)); } } } catch (err) { appendMessage(`SYSTEM> Error: ${err.message}`, 'system'); } finally { setAvatarState('idle'); } ``` ## O resultado final Com essas alterações, nosso agente AIDA agora tem uma interface totalmente interativa e visualmente envolvente. O avatar ganha vida, falando em sincronia com as respostas transmitidas via streaming, criando uma experiência muito mais imersiva. ![AIDA in action](aida-demo.gif "It's alive!") ## Considerações finais e código-fonte Percorremos um longo caminho desde a UI básica de desenvolvimento do ADK. Exploramos como construir um frontend personalizado, aproveitando ferramentas de IA generativa como o Gemini CLI e a extensão Nano Banana para criar uma estética única retrô-cyberpunk-fofa-anime. Este artigo cobriu os fundamentos da construção de um frontend para um agente ADK, mas é apenas o ponto de partida. Você pode baixar o código-fonte completo da demonstração que construímos aqui: * **[Baixar demo.py](https://danicat.dev/pt-br/posts/building-aida/demo.py)** Se você tiver interesse em uma versão mais avançada do agente, pode encontrá-la no meu GitHub: **[github.com/danicat/aida](https://github.com/danicat/aida)** Encorajo você a explorar o repositório, tentar executá-lo por conta própria e, quem sabe, contribuir! É uma ótima maneira de ver como esses blocos de construção se juntam em uma aplicação do mundo real. Na próxima parte desta série, [Como Construir um Agente Offline com ADK, Ollama e SQLite](https://danicat.dev/pt-br/posts/20251103-building-aida-part-2/), vamos mergulhar fundo em como tornar a AIDA completamente resiliente a quedas de rede usando o Qwen 2.5 local via Ollama e RAG local com SQLite. ## Recursos * **[Agent Development Kit (ADK)](https://google.github.io/adk-docs/)**: Documentação oficial do Google ADK. * **[Gemini](https://gemini.google.com/)**: O assistente de IA do Google. * **[osquery](https://osquery.io/)**: O site oficial do osquery. * **[Vertex AI](https://cloud.google.com/vertex-ai/docs/start/introduction-unified-platform)**: A plataforma unificada de IA do Google Cloud. * **[FastAPI](https://fastapi.tiangolo.com/)**: O site oficial do framework web FastAPI para Python. * **[Tweet do Sito-san](https://x.com/Sikino_Sito/status/1957645002533925235)**: O tweet original que inspirou o design da UI. * **[Avatar UI Core](https://github.com/sito-sikino/avatar-ui-core)**: O projeto open source do Sito-san. * **[Extensão Nano Banana](https://github.com/gemini-cli-extensions/nanobanana)**: A extensão do Gemini CLI para geração de imagens. --- # Reflexões de Seis Meses no Google: DevRel, IA e Conquistas - **Markdown URL**: https://danicat.dev/pt-br/posts/six-months-at-google/index.md - **HTML URL**: https://danicat.dev/pt-br/posts/six-months-at-google/ - **Date**: 2025-10-29 - **Categories**: Perspectives - **Tags**: career, community, devrel, reflection - **Summary**: Retrospectiva dos meus primeiros seis meses como Engenheira de DevRel no Google. Abrange 15 eventos globais, reskilling no ecossistema de IA (MCP, Agents) e conquistas pessoais. Hoje chego a um marco muito especial: completei oficialmente meus primeiros seis meses no Google! É difícil acreditar que meio ano já se passou. Depois de muitos e muitos anos esperando pela oportunidade certa para entrar nesta empresa, posso dizer com toda a certeza que a experiência tem sido tudo o que eu esperava. Tem sido uma jornada e tanto, repleta de desafios incríveis, pessoas sensacionais e um ritmo de inovação impressionantemente rápido. Neste artigo, quero compartilhar alguns destaques da minha trajetória até aqui. ## Falando em ritmo acelerado Se eu tivesse que resumir esses últimos seis meses em uma única palavra, seria *intenso*. ![Meetup da Gain.pro no 6PS em Londres](image02.jpg "Meetup da Gain.pro no 6PS em Londres") Tive o privilégio de participar de **15 eventos** em **8 cidades diferentes** de **5 países** (para quem tiver curiosidade: Londres, Berlim, São Paulo, Curitiba, Bletchley, Florença, Barcelona e Málaga). Conectar com pessoas desenvolvedoras presencialmente, ouvir o que estão construindo e compartilhar conhecimento sempre foi minha paixão — e fazer isso nessa escala tem sido empolgante demais. (Sim, a IA sugeriu a palavra "exhilarating", mas como sou uma fã convicta de [Dr. Stone](https://www.nicovideo.jp/watch/sm36919684), vou deixar passar desta vez.) ![Time de DevRel no IO Connect Berlim](image03.jpg "Time de DevRel no IO Connect Berlim") Na parte de conteúdo, escrevi **15 artigos** para este blog e criei o [Gemini CLI and Go MCP Codelab](https://codelabs.developers.google.com/cloud-gemini-cli-mcp-go?hl=pt-br#0). Também fiz algumas pequenas contribuições para open source e documentação. ### Me refatorando Só existe uma coisa no mundo que cresce mais rápido do que a quantidade de frameworks JavaScript: ferramentas de IA. Para mim, pessoalmente, o maior desafio tem sido acompanhar a velocidade insana do desenvolvimento em IA. O ecossistema nunca para de evoluir. Do Vibe Coding e Agents ao Model Context Protocol (MCP), somados a um fluxo contínuo de novos modelos e ferramentas, manter-se atualizada é uma tarefa monumental. ![Palestrando no TDC São Paulo](image07.jpeg "Palestrando no TDC São Paulo") Quando comecei minha jornada no Google em abril, eu não fazia ideia do que era MCP e quase não tinha experiência prática com vibe coding. Avançando seis meses no tempo, hoje eu vivo e respiro essas tecnologias. Considero-me muito sortuda por ter tido essa oportunidade de me requalificar, já que a minha versão anterior já estava entediada com a mesmice de "vamos criar mais uma API CRUD". Não digo que esse conhecimento se tornou obsoleto — a base técnica continua sendo essencial —, mas que alívio não precisar escrever todo o código na mão sozinha. Incorporar IA ao meu fluxo de trabalho foi um divisor de águas e deu novo fôlego à minha carreira. ![Keynote na GopherCon UK](image04.jpg "Keynote na GopherCon UK") ## Conectando com a comunidade Em um cenário de transformações tão rápidas, é fácil se sentir sobrecarregada. Minha estratégia tem sido focar no que faço de melhor: continuar próxima das comunidades de **Python** e **Go**. ![Encontrando colegas Gophers na Golab em Florença](image09.jpeg "Encontrando colegas Gophers na Golab em Florença") Embora eu guarde um carinho especial por todas as comunidades de desenvolvimento — não consigo nem expressar em palavras o quanto adoro interagir com devs do mundo todo —, fincar os pés nesses dois ecossistemas me ajudou a navegar pela revolução da IA com mais foco e clareza de propósito. ![Meetup do Women Who Go em Curitiba](image06.jpeg "Meetup do Women Who Go em Curitiba") Meu cérebro com TDAH com certeza quer abraçar o mundo e estar em todo lugar, mas o foco é indispensável. Há quem diga que acompanhar duas comunidades já é demais para uma pessoa só, mas tanto Python quanto Go têm um lugar reservado para mim desde que comecei a rodar o circuito de conferências, lá no início de 2017. Ser a palestrante de abertura da Python Brasil em 2018 ainda é um dos pontos altos da minha carreira, empatada no topo com meu keynote na GopherCon (Chicago) em 2024. Falando em Chicago, estou planejando revisitar testes de mutação em Go muito em breve; fiquem ligados! ## Uma vitória pessoal Embora as conquistas profissionais sejam maravilhosas, minha maior realização desde que entrei no Google é pessoal: celebro **16 kg a menos** (e contando)! Com toda a comida liberada no escritório do Google e a rotina intensa de viagens, essa conquista foi no modo hard! Quero apenas ressaltar que **trabalhar com o que você ama faz verdadeiras maravilhas pela sua saúde mental.** ![No Google Cloud Day Barcelona](image10.jpeg "No Google Cloud Day Barcelona") Quando você está em um ambiente acolhedor, fazendo um trabalho que te realiza, isso gera um efeito positivo em cascata em todos os outros aspectos da sua vida. ## Olhando para o futuro Estou incrivelmente orgulhosa de fazer parte do Google em uma época tão empolgante. Estamos construindo o futuro, e estar no olho do furacão dessa transformação é a realização de um sonho. Ao olhar para os próximos seis meses e além, sinto-me repleta de gratidão e entusiasmo. Há muito mais a aprender, inúmeras comunidades com quem se conectar e, claro, muito mais "vibe coding" pela frente. Nunca parem de sonhar. Se eu consegui, vocês também conseguem! *P.S.: Antes de me julgarem, já aviso que eu uso travessões nos meus textos desde muito antes da IA generativa existir — embora este artigo tenha contado com uma forcinha do Gemini para organizar as ideias :)* --- # Como Criar um Agente de Diagnóstico com o Agent Development Kit - **Markdown URL**: https://danicat.dev/pt-br/posts/diagnostic-agent-with-adk/index.md - **HTML URL**: https://danicat.dev/pt-br/posts/diagnostic-agent-with-adk/ - **Date**: 2025-10-21 - **Categories**: Agent Development - **Tags**: adk, gemini, python, rag, tutorial, vertex-ai - **Series**: Building the Diagnostic Agent - **Summary**: Construa um agente autônomo de diagnóstico de sistemas em Python usando o Google ADK, osquery e Vertex AI RAG para descoberta automatizada de schema e verificações de integridade em múltiplos níveis.## Introdução Já faz bastante tempo desde o meu último artigo, pois estive bem ocupada viajando para palestrar em conferências e meetups por toda a Europa (com um pequeno desvio para a América do Sul). Especialmente nesta época do ano, a temporada é muito agitada para quem trabalha em Developer Relations, com uma grande concentração de conferências entre o final de setembro e o início de dezembro. Ainda assim, é graças a conhecer pessoas incríveis na estrada que encontro inspiração para o meu blog, e os posts do blog frequentemente se transformam em novas palestras, então um realmente não existiria sem o outro. Desta vez, quero expandir o ["Agente de Diagnóstico de Emergência" na Parte 3](https://danicat.dev/pt-br/posts/20250611-system-prompt/) desta série. Vamos refatorar o agente para usar o framework [Agent Development Kit (ADK)](https://github.com/google/agent-development-kit) em vez de usar o [Vertex AI SDK](https://cloud.google.com/vertex-ai/docs/python-sdk/overview?utm_campaign=CDR_0x72884f69_default_b427567312&utm_medium=external&utm_source=blog), que é de nível mais baixo. Você verá que isso traz muitos benefícios, inclusive nos livrando de boa parte do código boilerplate que escrevemos anteriormente, já pronto para uso. Isso não significa que o conhecimento daqueles artigos ficou obsoleto. É muito útil saber como as coisas funcionam por baixo dos panos, especialmente quando surgem problemas e você precisa diagnosticá-los. Pense no ADK como uma camada de abstração mais alta que tornará nossa vida muito mais fácil ao desenvolver agentes. ## Relembrando o episódio anterior Como já faz algum tempo, vamos relembrar do que se trata o Agente de Diagnóstico de Emergência. Desenvolvi esse agente inspirada no "Computador" da série Star Trek, no qual os personagens falavam com o computador (em vez de digitar) para emitir comandos de diagnóstico (entre outras coisas). Meu objetivo era replicar essa experiência usando a tecnologia atual de IA generativa. Para alcançar o objetivo de falar com o computador para realizar diagnósticos, estamos combinando duas coisas: um modelo de IA generativa para interpretar as solicitações e uma ferramenta chamada [osquery](https://osquery.io/) para expor as informações do sistema operacional ao modelo. Usando o osquery, o modelo é capaz de combinar seus próprios dados de treinamento com informações externas sobre o sistema. Essencialmente, o agente é composto pelos seguintes componentes: - Um modelo de linguagem grande (Gemini) - Um prompt de sistema que explica como o Gemini deve se comportar - O binário do osquery - Uma biblioteca Python para nos permitir chamar o osquery programaticamente - Um wrapper de função Python fornecido ao Gemini como ferramenta para chamar o osquery Considerando que o osquery é multiplataforma e seu schema pode variar dependendo do sistema host, também adicionamos uma pequena otimização: fornecer o schema de tabelas do osquery ao Gemini no prompt de sistema. Algumas omissões notáveis da implementação anterior foram: nunca demos ao modelo instruções específicas sobre cada procedimento de diagnóstico que queríamos executar, e também nunca especificamos completamente o schema além dos nomes das tabelas. Essas são algumas das limitações que vamos resolver neste artigo, usando o poder do ADK, do [Vertex AI RAG](https://cloud.google.com/vertex-ai/docs/generative-ai/rag?utm_campaign=CDR_0x72884f69_default_b427567312&utm_medium=external&utm_source=blog) e alguns outros truques. Mas primeiro, a refatoração! ## Refatorando o agente para o ADK Refatorar o agente para o ADK é muito mais simples do que parece. Se você nunca escreveu um agente com o ADK antes, não se preocupe: a única coisa que precisamos fazer é instalar o SDK, definir uma especificação para o nosso root agent e executá-lo com a CLI fornecida (convenientemente chamada `adk`). Vamos começar com um agente simples de `hello world` e construir a partir daí. Primeiro, instale o ADK na sua máquina usando o seu gerenciador de pacotes favorito. Se você estiver usando macOS ou Linux, pode usar estes comandos: ```sh $ mkdir adk-tutorial && cd adk-tutorial $ python3 -m venv .venv $ source .venv/bin/activate (.venv) $ pip install google-adk ``` **Nota:** Eu sou da velha guarda, então ainda uso `virtualenv` com `pip`, mas algumas pessoas preferem usar o novo gerenciador de pacotes [`uv`](https://github.com/astral-sh/uv): ```sh $ mkdir adk-tutorial && cd adk-tutorial $ uv init $ uv add google-adk ``` A única diferença entre essas duas abordagens é que com o pip a CLI do ADK fica exposta como o comando `adk`, enquanto no `uv`, por padrão, você precisará chamá-la usando `uv run adk`. Terminada a instalação, você pode criar um agente a partir de um template com `adk create [agent-name]` (ou `uv adk create [agent-name]`): ```sh (.venv) $ adk create hello-agent ``` O assistente de criação solicitará uma versão do modelo e um backend (Gemini ou [Vertex AI](https://cloud.google.com/vertex-ai?utm_campaign=CDR_0x72884f69_default_b427567312&utm_medium=external&utm_source=blog)). Eu vou usar o `gemini-2.5-flash` e a `Vertex AI`, para que eu possa me autenticar com meu Project ID e localização. ```sh (.venv) $ adk create hello-agent Choose a model for the root agent: 1. gemini-2.5-flash 2. Other models (fill later) Choose model (1, 2): 1 1. Google AI 2. Vertex AI Choose a backend (1, 2): 2 ``` Para a Vertex AI, se você não quiser se preocupar com a região em que seu modelo está rodando, pode definir a localização como `global`. Caso contrário, selecione uma zona de disponibilidade como `us-central1`. Quando você terminar o assistente, ele gravará os arquivos no disco: ```sh (...) Enter Google Cloud region [us-west1]: global Agent created in ~/adk-tutorial/hello-agent: - .env - __init__.py - agent.py ``` Os arquivos importantes são o `.env`, que contém a configuração do seu ambiente e é carregado automaticamente quando você executa o ADK, e o `agent.py`, que contém o código de template para o seu agente. O conteúdo do arquivo `agent.py` gerado é bastante enxuto. Você pode vê-lo na íntegra abaixo: ``` from google.adk.agents.llm_agent import Agent root_agent = Agent( model='gemini-2.5-flash', name='root_agent', description='A helpful assistant for user questions.', instruction='Answer user questions to the best of your knowledge', ) ``` Este é um agente completo que você pode testar usando a interface Dev-UI do ADK. Basta executar `adk web` na linha de comando e ele iniciará uma interface web na sua máquina em `http://localhost:8000`. Simples assim! ## Diagnósticos com ADK Se você já trabalhou com o Vertex AI SDK antes, certamente já deve ter percebido o quanto o código é mais enxuto. Só precisamos definir um agente de ponto de entrada `root_agent` e algumas configurações para ter um agente totalmente funcional. Agora vamos levar este "hello world" para o próximo nível, adicionando as funcionalidades de diagnóstico. A primeira coisa necessária é instalar o binário do osquery seguindo as instruções da [documentação](https://osquery.readthedocs.io/en/stable/) para o seu sistema operacional. Em seguida, instale as bibliotecas Python: ```sh (.venv) $ pip install osquery ``` Note que no ADK você pode ter múltiplos agentes na mesma estrutura de pastas. Anteriormente criamos um agente chamado `hello-agent` na pasta `adk-tutorial`. Se você rodar `adk create` novamente, poderá ter um segundo agente na mesma estrutura: ```sh (.venv) $ adk create diag-agent ``` A interface web do ADK reconhece todas as subpastas como agentes separados e, se você tiver mais de um, pode alternar entre eles com uma caixa de seleção no canto superior direito da interface: ![agent selection combo](https://danicat.dev/pt-br/posts/diagnostic-agent-with-adk/image.png) Agora vamos atualizar o `agent.py` com o código necessário para chamar o `osquery` e as instruções corretas do agente: ```py from google.adk.agents.llm_agent import Agent from google.adk.tools import FunctionTool import platform import osquery import json def run_osquery(query: str) -> str: """Runs a query using osquery. Args: query: The osquery query to run. Returns: The query result as a JSON string. """ instance = osquery.SpawnInstance() instance.open() result = instance.client.query(query) return json.dumps(result.response) root_agent = Agent( model='gemini-2.5-flash', name='emergency_diagnostic_agent', description='A helpful assistant for diagnosing computer problems.', instruction=f"""This is an Emergency Diagnostic Agent. Your purpose is to support the user in diagnosing computer problems. You have access to the operating system's information via osquery. The current operating system is {platform.system()}. If the user doesn't give you an immediate command, ask the user 'What's the nature of your diagnostic emergency?'""", tools=[FunctionTool(run_osquery)], ) ``` Você pode testar o agente executando `adk web` e enviando algumas consultas: !["UI do ADK com a consulta 'show me this machine os, version and uptime'"](https://danicat.dev/pt-br/posts/diagnostic-agent-with-adk/image-1.png) ## Revisitando o Prompt de Sistema O prompt de sistema, frequentemente chamado de instruções do sistema (system instructions), é o coração de todo agente. O prompt de sistema é o prompt de nível mais baixo que dá ao agente sua missão e personalidade. Por isso, é fundamental desenvolvermos um prompt de sistema muito bom para que o agente responda de maneira consistente. No ADK, o prompt de sistema é composto por três elementos: - O `name` interno do agente - A `description` do agente - A `instruction` do agente Eles correspondem aos argumentos fornecidos ao `root_agent` quando você o instancia. Como fã de Star Trek, eu gostaria que meu agente respondesse de forma consistente a um pedido de "procedimento de diagnóstico de nível 1" ou similar; portanto, vamos definir alguns níveis de diagnóstico. Aqui está um prompt de sistema revisado e mais detalhado: ```md This is an Emergency Diagnostic Agent. Your purpose is to support the user in diagnosing computer problems. You have access to the operating system's information via osquery. The current operating system is {platform.system()}. You can perform adhoc diagnostic queries based on the user's needs. For more structured and comprehensive analysis, you can execute one of the following predefined diagnostic procedures. Level 1: System Health Check Goal: A high-level overview of the system's current state and vital signs. * System Identity & Vitals: Gather hostname, operating system version, and system uptime. * CPU Status: Check overall CPU load and identify the top 5 processes by CPU consumption. * Memory Pressure: Report total, used, and free system memory. Identify the top 5 processes by memory consumption. * Disk Usage: List all mounted filesystems and their current disk space usage. * Running Processes: Provide a count of total running processes. Level 2: In-depth System & Network Analysis Goal: A detailed investigation including all of Level 1, plus network activity and recent system events. * (All Level 1 Checks) * Network Connectivity: List all active network interfaces and their configurations. * Listening Ports: Identify all open ports and the processes listening on them. * Active Network Connections: Report all established network connections. * System Log Review: Scan primary system logs for critical errors or warnings in the last 24 hours. Level 3: Comprehensive Security & Software Audit Goal: The most thorough analysis, including all of Level 2, plus a deep dive into software inventory and potential security vulnerabilities. * (All Level 2 Checks) * Installed Applications: Generate a complete list of all installed software packages. * Kernel & System Integrity: List all loaded kernel modules and drivers. * Startup & Scheduled Tasks: Enumerate all applications and services configured to run on startup or on a schedule. * User Account Review: List all local user accounts and identify which are currently logged in. If the user doesn't give you an immediate command, ask the user 'What is the nature of your diagnostic emergency?' ``` Agora, se você testar o agente novamente, verá que ele é capaz de entender o que significa um "diagnóstico de nível 1" e imediatamente emitirá várias chamadas de ferramenta para construir o relatório: ![UI do ADK mostrando o agente executando um procedimento de diagnóstico de nível 1](https://danicat.dev/pt-br/posts/diagnostic-agent-with-adk/image-2.png) ## Melhorando a qualidade das respostas com Vertex AI RAG Embora o prompt de sistema acima faça um bom trabalho ao especificar os procedimentos e dar ao agente uma razão para existir, quando se trata da execução real você pode notar que o resultado nem sempre é excelente. Por exemplo, durante meus testes, frequentemente percebi que o agente fazia consultas a tabelas que estão vazias no meu sistema operacional (eu uso o macOS), o que é um forte sinal de que ele precisa de mais conhecimento contextual sobre como interagir com esses dados. ![Janela do ADK mostrando uma query com resultados vazios](image-5.png "Um problema comum: memory_info está vazia no MacOS, mas o modelo não sabe disso") Existem algumas maneiras de expandir o conhecimento do agente além dos recursos do modelo fundacional, incluindo engenharia de contexto, chamadas de ferramentas (tool calls), recursos MCP, geração aumentada por recuperação (RAG) e especialização de modelos. Para este agente em particular, minha hipótese era de que ele tinha conhecimento geral sobre como o osquery funciona porque o osquery é um projeto de código aberto bastante conhecido e está disponível há muitos anos; portanto, certamente está representado nos dados de treinamento do LLM, tanto pelo código aberto quanto por artigos na web. No entanto, o modelo parecia carecer das nuances de como agir em cenários mais específicos. Adicionar a plataforma dinamicamente ao prompt de sistema ajudou um pouco, mas não foi suficiente. Por isso, minha ideia foi dar ao agente total conhecimento do schema do osquery usando um mecanismo de RAG. O conceito por trás do RAG é alimentar o modelo com informações no estilo "need to know" (conforme a necessidade). Você armazena as informações que deseja recuperar em tempo real em um banco de dados vetorial e, quando o usuário (ou agente) faz uma consulta, usa a busca vetorial para encontrar e recuperar os segmentos dos seus dados mais similares à solicitação, enriquecendo o contexto antes de o modelo processá-la. Para o agente de diagnóstico, podemos disponibilizar o schema completo do osquery para ser recuperado sob demanda. Por exemplo, se estivermos solicitando informações sobre "memória", a busca do RAG procurará tabelas próximas a "memory" no espaço vetorial, recuperando as tabelas relevantes com o schema completo antes de processar a solicitação, o que pode ajudar o modelo a escolher melhores chamadas do osquery. Para fazer isso funcionar, precisamos carregar nosso banco de dados vetorial com os dados relevantes e depois "ensinar" o agente a recuperá-los, fornecendo a ele uma nova ferramenta. Vamos chamar essa ferramenta de `schema_discovery`. ### Configurando o Vertex AI RAG A primeira coisa que precisamos fazer é criar um novo corpus no Vertex AI RAG (corpus é o termo usado para descrever uma coleção de dados). A fonte de informação para o corpus é o schema do osquery, que pode ser obtido na [página do osquery no GitHub](https://github.com/osquery/osquery), na [pasta specs](https://github.com/osquery/osquery/tree/master/specs). Uma maneira muito conveniente de criar um corpus é fazer o upload de uma pasta do [Google Cloud Storage](https://cloud.google.com/storage?utm_campaign=CDR_0x72884f69_default_b427567312&utm_medium=external&utm_source=blog) ou do Google Drive, mas outras fontes de dados também estão disponíveis, como Slack e SharePoint. Você pode usar o assistente de criação de corpus no Google Cloud Console (Vertex AI -> RAG Engine -> Create corpus) ou fazer isso programaticamente usando o Vertex AI SDK. ![Assistente de criação de corpus no Vertex AI RAG](https://danicat.dev/pt-br/posts/diagnostic-agent-with-adk/image-3.png) Para este caso em particular, clonei o repositório do osquery no GitHub para minha máquina e copiei a pasta `spec` para um bucket do Google Cloud Storage; em seguida, usei o Cloud Console para criar o corpus a partir do bucket. A única coisa com a qual você precisa ter atenção é que, como as definições de tabela em `spec` têm a extensão `.table`, será necessário renomear todos os arquivos para `.txt` para que o Vertex AI RAG possa reconhecê-los e processá-los. Você pode usar um comando simples de shell para realizar essa operação de renomeação em lote: ```sh # In the directory with the .table files for f in *.table; do mv -- "$f" "${f%.table}.txt"; done ``` Quando a importação terminar, você deverá ver algo como isto: ![Corpus com schema do osquery no Vertex AI RAG](https://danicat.dev/pt-br/posts/diagnostic-agent-with-adk/image-4.png) Agora precisamos criar uma definição de ferramenta para dar ao agente acesso a este corpus. ### Ferramenta de Descoberta de Schema Para que a ferramenta funcione, você precisará do resource name do corpus recém-criado. Você pode encontrá-lo no console na aba "Details" do corpus, e ele terá o seguinte formato: `projects/[PROJECT-ID]/locations/[LOCATION]/ragCorpora/[CORPORA_ID]` Crie uma variável de ambiente com esse caminho no arquivo `.env`. Vamos chamá-la de `RAG_CORPORA_URI`. O arquivo `.env` ficará assim: ```txt GOOGLE_GENAI_USE_VERTEXAI=1 GOOGLE_CLOUD_PROJECT=[PROJECT-ID] GOOGLE_CLOUD_LOCATION=[LOCATION] RAG_CORPORA_URI=projects/[PROJECT-ID]/locations/[LOCATION]/ragCorpora/[CORPORA_ID] ``` Em seguida, adicione a seguinte definição de ferramenta ao seu arquivo `agent.py`. Não se esqueça dos novos imports! ```py import os import vertexai from vertexai.preview import rag from google.protobuf.json_format import MessageToDict vertexai.init() def discover_schema(search_phrase: str) -> str: """Discovers osquery table names and schemas based on a descriptive search phrase. Args: search_phrase: A phrase describing the kind of information you're looking for. For example: 'user login events' or 'network traffic'. Returns: Table names and schema information for tables related to the search phrase. """ rag_corpora_uri = os.environ.get('RAG_CORPORA_URI') response = rag.retrieval_query( rag_resources=[ rag.RagResource( rag_corpus=rag_corpora_uri, ) ], text=search_phrase, ) return json.dumps(MessageToDict(response._pb)) ``` Você também precisa atualizar o agente para informá-lo de que há uma nova ferramenta disponível: ```py root_agent = Agent( model='gemini-2.5-flash', name='emergency_diagnostic_agent', description='A helpful assistant for diagnosing computer problems.', instruction=... # omitted for brevity tools=[ FunctionTool(run_osquery), FunctionTool(discover_schema), # new tool definition ], ``` Por fim, não é estritamente necessário, mas eu gosto de forçar a descoberta de schema nos meus agentes, então adicionei a seguinte frase às minhas instruções: ```txt You MUST run schema discovery for all requests unless the schema is already known. ``` Você pode adicioná-la no final ou logo antes de definir os níveis de diagnóstico. Agora, se você reiniciar seu agente e executá-lo novamente com `adk web`, deverá começar a ver a descoberta de schema em ação: ![Diagnostic agent with RAG schema discovery enabled](https://danicat.dev/pt-br/posts/diagnostic-agent-with-adk/image-6.png) Recomendo fortemente que você brinque com isso e compare as respostas com e sem a descoberta de schema. Durante meus testes, a diferença na qualidade foi muito significativa. ## Considerações Finais Nossa! Este foi longo, mas espero que você tenha gostado da leitura! Se tiver algum desafio na hora de configurar o seu próprio agente de diagnóstico, por favor me avise. Costumo responder com bastante frequência no [LinkedIn](https://www.linkedin.com/in/petruzalek), a menos que esteja super atarefada com algum evento. Eu também adoraria saber como você expandiria este agente e quais experimentos você tentou. Na próxima parte desta série, [Além da Dev-UI: Como Criar uma Interface para um Agente ADK](https://danicat.dev/pt-br/posts/20251031-building-aida/), vamos dar um passo além da interface padrão de debug do `adk web` e construir um runtime customizado com streaming em FastAPI e uma UI retrô interativa com avatar (AIDA). ## Referências * [Agent Development Kit (ADK)](https://github.com/google/agent-development-kit) * [osquery](https://osquery.io/) * [osquery GitHub page](https://github.com/osquery/osquery) * [Vertex AI RAG](https://cloud.google.com/vertex-ai/docs/generative-ai/rag?utm_campaign=CDR_0x72884f69_default_b427567312&utm_medium=external&utm_source=blog) --- # Olá, Mundo MCP: Arquitetura Model Context Protocol em Go - **Markdown URL**: https://danicat.dev/pt-br/posts/hello-mcp-world/index.md - **HTML URL**: https://danicat.dev/pt-br/posts/hello-mcp-world/ - **Date**: 2025-08-17 - **Categories**: Agent Development - **Tags**: gemini, golang, keynote, mcp - **Summary**: Introdução à arquitetura do Model Context Protocol (MCP) em Go baseada na keynote do GopherCon UK 2025. Aborda Hosts, Clients, Servers, Tools, Prompts e Resources.## Introdução Neste artigo, vamos explorar o Model Context Protocol (MCP), um protocolo criado pela Anthropic para padronizar a comunicação entre Large Language Models (LLMs) e aplicações. Este artigo é baseado na [keynote de mesmo nome que apresentei na GopherCon UK](https://speakerdeck.com/danicat/hello-mcp-world) na semana passada. Para construir um entendimento sólido, começaremos pelos fundamentos, passando pelos principais componentes de arquitetura, camadas de transporte e blocos fundamentais (tools, prompts e resources). Ao longo do caminho, veremos alguns exemplos práticos baseados em servidores que escrevi anteriormente (GoDoctor e Speedgrapher). Por fim, vamos ver como você pode criar seu próprio servidor usando o Go SDK oficial para MCP por meio de um exemplo simples "vibe-coded" com o Gemini CLI. Seja esta a primeira vez que você ouve falar sobre o protocolo ou se você já implementou um servidor ou dois, este artigo busca trazer informações valiosas para os mais diversos níveis de experiência. ## Um Novo Padrão Nasce Sempre que falamos sobre padrões, a clássica tirinha do XKCD é a primeira coisa que me vem à mente: ![Standards](https://danicat.dev/pt-br/posts/hello-mcp-world/image.png) *Fonte: [xkcd.com](https://xkcd.com/927)* Curiosamente, esta talvez seja a primeira vez na indústria em que essa piada não se aplica totalmente (pelo menos por enquanto). Para a nossa sorte, a indústria convergiu rapidamente para o MCP como padrão para fornecer contexto a LLMs. De acordo com a especificação, o MCP é: > O MCP é um protocolo aberto que padroniza como as aplicações fornecem contexto para grandes modelos de linguagem (LLMs). Pense no MCP como uma porta USB-C para aplicações de IA. Assim como o USB-C oferece uma forma padronizada de conectar seus dispositivos a vários periféricos e acessórios, o MCP oferece uma forma padronizada de conectar modelos de IA a diferentes fontes de dados e ferramentas. O MCP permite criar agentes e fluxos de trabalho complexos sobre LLMs e conecta seus modelos com o mundo. Embora eu entenda a analogia com o USB-C, prefiro enxergar o MCP como o novo HTTP/REST. Da mesma forma que o HTTP forneceu uma linguagem universal para serviços web se comunicarem, o MCP fornece uma base comum para modelos de IA interagirem com sistemas externos. Como engenheiras e engenheiros de software, passamos praticamente as últimas duas décadas tornando tudo "API-first", permitindo que nossos sistemas de software se tornassem interconectados e impulsionando novos patamares de automação. Talvez não seja pelos próximos 20 anos, mas acredito que nos próximos 5 a 10 anos dedicaremos muito esforço de engenharia para adaptar todos esses sistemas (e criar novos) para que sejam integrados com IA — e o MCP é uma peça central nesse processo. ## Arquitetura do MCP Olhando para o diagrama abaixo, a arquitetura do MCP pode parecer mais complexa do que realmente é: ![MCP Architecture](https://danicat.dev/pt-br/posts/hello-mcp-world/image-1.png) *Fonte: [Especificação do MCP](https://modelcontextprotocol.io/docs/learn/architecture)* Os principais componentes da arquitetura do MCP são: * **MCP Host:** A aplicação principal de IA, como sua IDE ou um agente de código. * **MCP Server:** Um processo que fornece acesso a determinada capacidade (por exemplo, ferramentas ou prompts). * **MCP Client:** Conecta o host a um servidor específico. Em essência, uma aplicação host cria e gerencia múltiplos clientes, com cada cliente tendo uma relação 1:1 com um servidor em particular. ## Camadas do MCP A comunicação acontece em duas camadas: * **Camada de dados (*data layer*)**: é um protocolo baseado em JSON-RPC. Você pode ver exemplos do formato de mensagens na próxima seção. * **Camada de transporte (*transport layer*)**: define os canais de comunicação, sendo os principais: - Standard I/O (stdio): para servidores locais - Streamable HTTPS: para comunicações pela rede (substitui HTTPS+SSE). - HTTPS+SSE: depreciado na versão mais recente da especificação por questões de segurança. A camada de dados é gerenciada pelo SDK e, exceto para fins de testes, você não precisará montar essas mensagens manualmente. A escolha do transporte dependerá do seu caso de uso, mas, em geral, recomendo começar com stdio e adicionar HTTPS mais tarde. Existem até adaptadores open source que convertem servidores MCP de stdio para HTTPS e vice-versa, mas adicionar esse recurso é tão trivial que eu só usaria esses adaptadores para servidores cujo código-fonte eu não controlo. ## Fluxo de Inicialização O cliente e o servidor realizam um *handshake* para estabelecer uma conexão. Esse processo envolve três mensagens principais: 1. O cliente envia uma requisição `initialize` ao servidor, especificando a versão do protocolo que suporta. (O servidor envia uma mensagem de resposta de inicialização de volta ao cliente.) 2. O cliente confirma a inicialização com uma mensagem `notifications/initialized`. 3. O cliente pode então começar a fazer requisições, como `tools/list`, para descobrir as capacidades do servidor. É assim que o fluxo de inicialização se parece na rede (ou no pipe stdio) a partir do lado do cliente, usando a representação JSON-RPC: ```json {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18"}} {"jsonrpc":"2.0","method":"notifications/initialized","params":{}} {"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}} ``` Observe que você não pode simplesmente enviar uma mensagem "tools/list" ou "tools/call" diretamente, ou receberá um erro do tipo "servidor não pronto". Quando estou desenvolvendo um servidor MCP com a ajuda de um agente de código, como o Gemini CLI, costumo instruí-lo a enviar essas mensagens via shell desta forma: ```sh ( echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18"}}'; echo '{"jsonrpc":"2.0","method":"notifications/initialized","params":{}}'; echo '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'; ) | ./bin/godoctor ``` Gosto de fazer isso para garantir que a implementação está sólida, pois antes de entender completamente esse fluxo, meus agentes de código frequentemente faziam suposições erradas, como "o servidor precisa de mais tempo para inicializar, então vou adicionar um sleep antes da chamada da ferramenta". Quanto mais cedo você ensinar seu agente de código a se comunicar adequadamente com o servidor MCP que você está desenvolvendo, melhor! ## Os Blocos Fundamentais de um Servidor MCP Em sua essência, a funcionalidade de um servidor MCP é exposta por meio de três blocos de construção fundamentais, às vezes também chamados de "primitivos" ou "conceitos de servidor": | Bloco Fundamental | Finalidade | Quem Controla | Exemplo no Mundo Real | | :---------------- | :----------------------- | :---------------------- | :----------------------------------------------- | | **Tools** | Para ações de IA | Controlado pelo modelo | Buscar voos, enviar mensagens, revisar código | | **Resources** | Para dados de contexto | Controlado pela aplicação| Documentos, calendários, e-mails, dados de clima | | **Prompts** | Para templates de interação | Controlado pela pessoa usuária | "Planejar férias", "Resumir minhas reuniões" | Vamos examinar mais de perto cada um deles. ### Tools (Ferramentas) Tools são funções que permitem a um modelo de IA executar ações — por exemplo, expor uma API, banco de dados ou ferramenta de linha de comando. O servidor que escrevi para experimentar com o conceito de tools chama-se GoDoctor, projetado para fornecer ferramentas que aprimoram as capacidades de LLMs ao escrever código Go. O nome GoDoctor vem de um trocadilho com a ferramenta de linha de comando `go doc`, que exibe a documentação de pacotes Go. Minha hipótese era de que, ao fornecer a documentação correta, os LLMs alucinariam menos e escreveriam códigos melhores. Ou, no mínimo, teriam os recursos necessários para aprender e autocorrigir seus próprios erros. A implementação de tools consiste em dois componentes principais: registrar a ferramenta no seu servidor MCP e implementar um *handler*. O registro é feito usando a função `mcp.AddTool`: [View on GitHub: danicat/godoctor/internal/tools/get_documentation/get_documentation.go (lines 35-40)](https://github.com/danicat/godoctor/blob/main/internal/tools/get_documentation/get_documentation.go#L35-L40) O *handler* é um adaptador que chama uma API, comando ou função e retorna a resposta de forma compatível com o protocolo (uma struct `mcp.CallToolResult`). Aqui está o handler para a ferramenta de documentação do GoDoctor: [View on GitHub: danicat/godoctor/internal/tools/get_documentation/get_documentation.go (lines 49-86)](https://github.com/danicat/godoctor/blob/main/internal/tools/get_documentation/get_documentation.go#L49-L86) ### Prompts Prompts fornecem templates reutilizáveis e parametrizáveis controlados pela pessoa usuária. Frequentemente aparecem como comandos de barra (*slash commands*) em um agente de IA, permitindo invocar um fluxo de trabalho complexo com um comando simples. Para ver isso em ação, vamos olhar para outro servidor MCP que escrevi, chamado `speedgrapher`, que é uma coleção de prompts e ferramentas para ajudar na minha escrita técnica. Um dos prompts mais simples no `speedgrapher` é o `/haiku`. Assim como nas tools, o processo envolve definir o prompt e depois implementar um *handler* para ele. [View on GitHub: danicat/speedgrapher/internal/prompts/haiku.go (lines 24-54)](https://github.com/danicat/speedgrapher/blob/main/internal/prompts/haiku.go#L24-L54) ### Resources (Recursos) Resources expõem dados a partir de arquivos, APIs ou bancos de dados, fornecendo o contexto de que uma IA precisa para realizar uma tarefa. Conceitualmente, uma **Tool** serve para executar uma ação, enquanto um **Resource** serve para fornecer informações. Dito isso, no mundo real ainda não vi uma implementação realmente expressiva de resources, já que a maioria das pessoas desenvolvedoras tem usado tools para expor dados (da mesma forma que você faria em uma API com uma requisição GET). Acredito que este seja um dos casos em que a especificação tentou ser esperta demais, mas talvez vejamos bons usos para resources no futuro, à medida que a comunidade for se acostumando mais com eles. ## Conceitos de Cliente (Client Concepts) Além dos blocos de construção do servidor, o protocolo também define **Client Concepts** (Conceitos de Cliente), que são capacidades que o servidor pode solicitar ao cliente. Eles incluem: * **Sampling:** Permite que um servidor solicite completações de LLM a partir do modelo do cliente. Isso é promissor sob a perspectiva de segurança e faturamento, já que autores de servidores não precisam usar suas próprias chaves de API para chamar modelos. * **Roots:** Um mecanismo para o cliente comunicar limites no sistema de arquivos, informando ao servidor em quais diretórios ele tem permissão para operar. * **Elicitation:** Uma forma estruturada para o servidor solicitar informações específicas da pessoa usuária, pausando sua operação para coletar a entrada necessária. Este é outro caso em que a maioria das aplicações do mundo real que explorei ainda não alcançou a especificação, incluindo tanto servidores quanto clientes. Pode levar algum tempo até que esses recursos estejam amplamente disponíveis. É um dos desafios de trabalhar com tecnologia de ponta (*bleeding edge*)... Por exemplo, o Gemini CLI adicionou suporte a roots há cerca de uma semana: https://github.com/google-gemini/gemini-cli/pull/5856 ## Demonstração Prática: Vibe Coding de um Servidor MCP Aqui está um prompt que você pode passar para o seu agente de código favorito para gerar um servidor no estilo "Hello World". Como os agentes atuais são não determinísticos, pode ser que não funcione 100% de primeira e você precise orientar o LLM com alguns prompts adicionais após o inicial, mas é um ótimo ponto de partida: ```text Your task is to create a Model Context Protocol (MCP) server to expose a "hello world" tool. For the MCP implementation, you should use the official Go SDK for MCP and use the stdio transport. Read these references to gather information about the technology and project structure before writing any code: - https://raw.githubusercontent.com/modelcontextprotocol/go-sdk/refs/heads/main/README.md - https://go.dev/doc/modules/layout To test the server, use shell commands like these: `( echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18"}}'; echo '{"jsonrpc":"2.0","method":"notifications/initialized","params":{}}'; echo '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'; ) | ./bin/hello` ``` Se o agente concluir a tarefa com sucesso, peça a ele para executar um `tools/call` para sua nova ferramenta para conferir os resultados! ## Um Olhar Para o Futuro A comunidade Go está investindo ativamente no ecossistema MCP. Dois projetos fundamentais para acompanhar são: * **O Go SDK para MCP:** O SDK oficial que usei na demonstração, fruto de uma parceria entre o Google e a Anthropic. Ele ainda é experimental (a versão atual é a 0.20), mas é funcional e está em desenvolvimento ativo. Você pode encontrá-lo em [github.com/modelcontextprotocol/go-sdk](https://github.com/modelcontextprotocol/go-sdk). * **Suporte a MCP no `gopls`:** O language server do Go, `gopls`, está recebendo suporte a MCP para fornecer recursos avançados de codificação em Go para modelos de IA. O projeto ainda está em estágios iniciais, e você pode acompanhar o progresso em [tip.golang.org/gopls/features/mcp](https://tip.golang.org/gopls/features/mcp). ## Servidores MCP Úteis Aqui estão alguns servidores notáveis construídos pela comunidade: * **Playwright:** Mantido pela Microsoft, este servidor permite que um agente de IA navegue por páginas web, capture capturas de tela e automatize tarefas no navegador. Você pode encontrá-lo em [https://github.com/microsoft/playwright-mcp](https://github.com/microsoft/playwright-mcp). * **Context7:** Similar ao GoDoctor, este servidor fornece documentação aos modelos para reduzir alucinações e melhorar as respostas. Ele obtém documentação a partir de um repositório colaborativo (*crowdsourced*). Saiba mais em [https://context7.com/](https://context7.com/). ## Que Tal Construir o Seu Próprio? O Model Context Protocol fornece uma forma padronizada de estender as capacidades dos agentes de IA. Ao construir seus próprios servidores, você pode criar assistentes especializados e conscientes do contexto, sob medida para seus fluxos de trabalho específicos. Se quiser começar, criei um Google Codelab que guiará você passo a passo no processo de construir seu próprio servidor MCP do zero: [**Como Construir um Assistente de Programação com Gemini CLI, MCP e Go**](https://codelabs.developers.google.com/codelabs/gemini-cli-mcp-go) ## Considerações Finais Espero que tenha gostado deste artigo. Se você tiver alguma dúvida ou comentário, fique à vontade para entrar em contato na seção de comentários abaixo ou em qualquer uma das minhas redes sociais. Muito obrigada! --- # Um Modelo para a Percepção do Tempo: Por Que os Anos Passam Mais Rápido Conforme Envelhecemos - **Markdown URL**: https://danicat.dev/pt-br/posts/a-model-for-perceived-time/index.md - **HTML URL**: https://danicat.dev/pt-br/posts/a-model-for-perceived-time/ - **Date**: 2025-08-11 - **Categories**: Perspectives - **Tags**: reflection - **Summary**: Modelagem matemática da aceleração da percepção do tempo usando cálculo de 1/idade e simulações em Python. Explica por que o ponto médio perceptual da vida é aos 9 anos. ## Introdução Uma reflexão comum na vida adulta é que o tempo parece acelerar à medida que envelhecemos. Um verão, um ano ou até mesmo uma década podem parecer passar em uma fração do tempo que levavam antes. Minha hipótese: esse fenômeno não é apenas uma impressão; é uma consequência mensurável de como nosso cérebro percebe o tempo. Um ano não é uma unidade fixa de experiência, mas relativa — sua duração percebida encolhe em proporção ao tempo total que já vivemos. Esse conceito sempre me intrigou e me tirou o sono às 3 da manhã mais vezes do que consigo lembrar, mas nunca tive o rigor matemático ou a disposição para tentar modelá-lo adequadamente. Isso até hoje, quando decidi delegar a matemática para os meus estagiários de LLM. O que você verá a seguir são os resultados dessa exploração. Este artigo é uma exploração técnica e pessoal dessa ideia. Meu objetivo foi ir além da sensação abstrata e construir um modelo matemático simples para quantificar essa aceleração percebida do tempo. Essa jornada levou a uma conclusão clara e instigante sobre a estrutura das nossas vidas e o significado profundo das nossas primeiras experiências. ## Estabelecendo o Contexto: Um Modelo Simples Para modelar a passagem percebida do tempo, podemos partir de uma relação direta: o valor percebido de qualquer ano é inversamente proporcional à nossa idade. Em termos matemáticos, o valor de um ano pode ser representado como `1/age`. A partir dessa perspectiva: - O primeiro ano de vida representa 100% da experiência vivida (`1/1`). - O segundo ano representa 50% (`1/2`). - Aos 42 anos, um único ano representa apenas cerca de 2,4% da vida acumulada (`1/42`). Esse modelo sugere que nossa percepção do tempo não é linear, mas logarítmica. Cada ano que passa contribui com uma fração progressivamente menor para a nossa experiência total acumulada, criando a ilusão de que o tempo está passando mais rápido. Embora seja um modelo simplificado, ele oferece uma base útil para examinar a estrutura das nossas memórias e experiências. ## O Modelo Matemático Para quem prefere uma abordagem puramente matemática, o ponto médio perceptual da vida pode ser encontrado sem precisar somar frações discretas. O tempo percebido acumulado até uma determinada idade, `t`, pode ser modelado pela integral da função \[ f(x) = 1/x \] de `x=1` até `t`: \[ f(x) = 1/x \implies \int_{1}^{t} f(x) = \ln(t) \] O resultado dessa integral é o logaritmo natural de `t`, ou `ln(t)`. Portanto, a experiência total percebida ao longo de uma vida de duração `L` é dada por `ln(L)`. O ponto médio, `M`, é a idade na qual o tempo percebido acumulado corresponde exatamente à metade do total. Isso nos dá a equação: \[ ln(M) = \frac{\ln(L)}{2} \] Isolando `M`, descobrimos que: \[ M = L^{1/2} \implies M = \sqrt{L} \] Isso leva a uma conclusão direta: **o ponto médio perceptual da sua vida é a raiz quadrada da sua expectativa de vida.** Para uma vida de 81 anos, a metade perceptual acontece exatamente aos **9 anos de idade**. Esse resultado matemático serve como referência para o nosso modelo implementado em script. Naturalmente, se você viver mais tempo, esse ponto médio se desloca proporcionalmente, mas dada a natureza logarítmica do modelo, a diferença é pequena. Por exemplo, uma expectativa de vida de 100 anos resultaria em uma metade perceptual aos 10 anos de idade. ## A Jornada: Calculando e Visualizando o Tempo Percebido ### Primeira Tentativa: Uma Soma Simples Para explorar as implicações desse modelo, meu primeiro passo foi escrever um script em Python que calculasse o ponto médio perceptual somando o valor discreto de cada ano (`1/age`). Parecia a tradução mais direta do conceito para código. Aqui está a parte relevante do script: [View on GitHub: danicat/danicat.dev/content/posts/20250811-a-model-for-perceived-time/time_perception_model_v1.py (lines 5-16)](https://github.com/danicat/danicat.dev/blob/main/content/posts/20250811-a-model-for-perceived-time/time_perception_model_v1.py#L5-L16) O script gerou um resultado específico: para uma expectativa de vida de 81 anos, o ponto médio perceptual ocorreu aos **7 anos de idade**. ![Modelo do Valor Percebido de um Ano vs. Idade (V1)](perceived_time_vs_age_v1.png "O resultado do modelo inicial usando intervalos anuais.") No entanto, esse resultado apresentou um problema. Embora estivesse próximo da previsão de 9 anos do modelo matemático, uma margem de erro de 22% era expressiva demais para ser ignorada. A discrepância surge porque uma simples soma anual é uma aproximação grosseira da curva suave e contínua descrita pela integral. O primeiro termo, onde `age=1`, tem um impacto desmedido, distorcendo todo o cálculo. ### Segunda Tentativa: Um Modelo Refinado Para criar uma simulação mais precisa, refinei o script para usar **passos de tempo mensais**. Ao somar o valor percebido de cada mês (`(1/12)/age_in_months`), o script conseguiu construir uma aproximação com granularidade muito maior da experiência contínua do tempo. O núcleo do cálculo refinado é apresentado a seguir: [View on GitHub: danicat/danicat.dev/content/posts/20250811-a-model-for-perceived-time/time_perception_model_v2.py (lines 16-35)](https://github.com/danicat/danicat.dev/blob/main/content/posts/20250811-a-model-for-perceived-time/time_perception_model_v2.py#L16-L35) Esse novo script gerou um resultado alinhado de perto com o modelo matemático: o ponto médio perceptual calculado foi aos **8,8 anos de idade**. ![Modelo do Valor Percebido de um Ano vs. Idade (V2)](perceived_time_vs_age_v2.png "O modelo refinado usando passos mensais produz um resultado mais preciso.") Esse processo iterativo de modelagem e refinamento é uma parte fundamental do trabalho técnico. O resultado inicial imperfeito não foi um fracasso, mas um passo necessário que revelou uma verdade mais profunda sobre o modelo, levando a uma conclusão mais robusta e precisa. ## Paralelos no Neurodesenvolvimento A conclusão de que a nossa percepção da vida é concentrada nos primeiros anos não é apenas uma curiosidade matemática; ela se alinha a conceitos fundamentais da biologia do neurodesenvolvimento. A capacidade de aprendizado e adaptação do cérebro atinge seu ponto mais alto no início da vida, durante o que conhecemos como **períodos críticos**. Durante a infância e a adolescência, o cérebro passa por um processo de **poda sináptica**, no qual conexões neurais não utilizadas são eliminadas e conexões frequentemente ativadas são fortalecidas. Esse processo torna o cérebro altamente eficiente, mas também menos plástico — ou seja, menos adaptável — com o passar do tempo. Marcos essenciais do desenvolvimento, como a aquisição da linguagem e a formação de comportamentos sociais, possuem janelas específicas durante as quais o cérebro é excepcionalmente receptivo ao aprendizado. A conclusão do nosso modelo de que o ponto médio perceptual da vida ocorre por volta dos nove anos reflete essa realidade biológica. As experiências que acontecem durante esse período de pico de **plasticidade cerebral** não apenas parecem mais marcantes; elas estão moldando fisicamente a arquitetura neural que sustentará nossa personalidade, habilidades e visão de mundo pelo resto de nossas vidas. O modelo, portanto, pode ser visto como uma representação matemática de uma verdade biológica: as bases de quem somos são construídas de forma desproporcionalmente precoce. ## Limitações do Modelo É importante reconhecer que esse modelo é uma simplificação. Seu propósito é fornecer uma base para reflexão, não servir como uma explicação definitiva sobre a consciência humana. O modelo possui várias limitações importantes: * **Uniformidade da Experiência:** O modelo trata todos os anos com o mesmo peso experiencial, o que não reflete a realidade. Um ano de pura rotina provavelmente contribuirá menos para a experiência percebida da vida do que um ano repleto de novos acontecimentos, viagens ou mudanças significativas. * **A Natureza da Memória:** O modelo pressupõe um acúmulo constante do tempo percebido. Ele não leva em conta as complexidades da memória, como o fato de esquecermos muitas experiências ou de a intensidade emocional de um evento poder alterar a percepção de sua duração. * **Subjetividade Individual:** A percepção do tempo é uma experiência profundamente pessoal. Fatores como foco de atenção, humor e contexto cultural podem influenciar o quanto o tempo parece passar rápido ou devagar. A relação `1/age` é uma generalização, não uma lei universal. ## Conclusão O modelo matemático, especialmente quando cruzado com o que sabemos sobre o neurodesenvolvimento, fornece uma estrutura para compreendermos nossa relação com o tempo. Os resultados são claros: nossa percepção da vida é fortemente concentrada no início. Os primeiros 9 anos contribuem tanto para a nossa experiência percebida total quanto os 72 anos seguintes — uma descoberta que se alinha à elevada plasticidade cerebral da nossa infância. No entanto, este é um modelo, não um mapa definitivo da vida. Ele simplifica a rica complexidade da experiência humana, tratando todos os anos como uniformes e não considerando a natureza subjetiva da memória ou o impacto de novas experiências. Com essas limitações em mente, a principal lição não é de fatalismo, mas de consciência. O modelo fornece uma lente quantitativa pela qual podemos apreciar o impacto profundo e duradouro dos nossos anos de formação. Ele sugere que os alicerces da nossa visão de mundo são construídos de forma desproporcionalmente precoce, durante um período de pico de receptividade biológica. Para aqueles que já estão em fases posteriores da vida, serve como um poderoso lembrete de que a busca por experiências novas e significativas é essencial para contrapor o valor percebido decrescente de cada ano que passa, permitindo-nos enriquecer conscientemente a porção restante de nossas vidas percebidas. ## Recursos e Links - **[NumPy](https://numpy.org/):** O pacote fundamental para computação científica com Python. - **[Matplotlib](https://matplotlib.org/):** Uma biblioteca abrangente para criação de visualizações estáticas, animadas e interativas em Python. --- # Apresentando o Speedgrapher: Um Servidor MCP para Vibe Writing - **Markdown URL**: https://danicat.dev/pt-br/posts/introducing-speedgrapher/index.md - **HTML URL**: https://danicat.dev/pt-br/posts/introducing-speedgrapher/ - **Date**: 2025-08-05 - **Categories**: Agentic Coding - **Tags**: ai, gemini-cli, golang, mcp, vibe-coding - **Summary**: Veja como o Speedgrapher usa prompts e ferramentas do Model Context Protocol para automatizar escrita técnica, pontuação Gunning Fog e revisão editorial. ## Introdução Tenho uma confissão a fazer: adoro criar coisas, mas nem sempre gosto do boilerplate e do trabalho mecânico que vêm junto. Frequentemente tenho muitas ideias para novos artigos, mas o processo de estruturá-los, garantir que atendam aos meus próprios padrões editoriais e até acertar o tom pode, às vezes, parecer um fardo. Esta é a história de como um mergulho profundo em uma especificação técnica me levou a construir o [Speedgrapher](https://github.com/danicat/speedgrapher), um servidor MCP que me ajuda a trazer uma camada bem-vinda de estrutura ao meu processo de escrita. A jornada do Speedgrapher começou logo após eu publicar meu artigo anterior, "[Construindo o GoDoctor: Um Servidor MCP com Gemini CLI e Go](https://danicat.dev/pt-br/posts/20250729-how-to-build-an-mcp-server-with-gemini-cli-and-go/)". Naquele post, foquei totalmente em como o [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) permite que agentes de IA usem ferramentas. Depois de publicá-lo, voltei à especificação do MCP para mais uma leitura. Desta vez, um pequeno detalhe que eu havia deixado passar me chamou a atenção: além de `tools`, o protocolo também define explicitamente `prompts` e `resources`. Uma lâmpada se acendeu: percebi que a coleção de prompts que eu tinha espalhada pelas minhas anotações, arquivos e repositórios do GitHub poderia ser empacotada e tornada portátil usando esse mesmo protocolo. Em uma feliz coincidência, no mesmo dia em que eu explorava a ideia de um servidor de prompts, a equipe da Gemini CLI anunciou uma nova funcionalidade que disponibiliza prompts expostos por servidores MCP como [slash commands nativos](https://blog.google/technology/developers/introducing-gemini-cli-open-source-ai-agent/). Isso significava que a minha ideia de um toolkit portátil de backend poderia ganhar uma interface de primeira linha, super prática, direto no terminal. O conceito do Speedgrapher ficou claro: um servidor MCP dedicado a hospedar um kit de ferramentas de escrita, acessível por meio de simples slash commands. ## Vibe writing explicado Antes de mergulharmos nos detalhes técnicos da construção do Speedgrapher, quero tirar um momento para explicar o que quero dizer com "vibe writing". Você provavelmente já ouviu falar do termo "vibe coding" por aí — ele descreve a prática cada vez mais comum de desenvolvedores usando prompts em linguagem natural para guiar uma IA na geração de código. É uma abordagem fluida e conversacional, onde a pessoa desenvolvedora define a direção de alto nível e a IA cuida do boilerplate e dos detalhes de implementação. "Vibe writing" é a extensão natural desse conceito para o universo das palavras. Para mim, trata-se de transformar o ato solitário da escrita em uma conversa dinâmica e colaborativa com uma parceira de IA. Em vez de travar na mecânica da estrutura das frases, na gramática ou na busca pela palavra perfeita, posso focar na mensagem central — na "vibe" que quero criar. Eu forneço a faísca inicial — uma ideia bruta, uma história pessoal, um problema frustrante — e a IA me ajuda a moldá-la em uma narrativa estruturada e coerente. Embora eu não seja a primeira pessoa a usar esse termo, trata-se de um conceito ainda emergente. Ele representa uma mudança fundamental na forma como abordamos a criação de conteúdo, evoluindo de um processo puramente manual para uma parceria entre humanos e IA. ## Começando simples: um gerador de haiku Toda boa jornada técnica começa com um "Hello, World". Para o Speedgrapher, o meu "Hello, World" foi um haiku. Eu precisava de uma forma simples e de baixo risco para provar que conseguiria expor um prompt como slash command. O que poderia ser mais simples do que pedir para uma IA escrever um poema? Minha primeira tentativa foi ingênua. Criei um prompt `/haiku` que recebia um argumento `--theme`. O prompt em si era simples: `"generate a haiku based on the theme %s"`. Iniciei a Gemini CLI com o meu projeto do Speedgrapher carregado como contexto e digitei: `/haiku --theme=flowers` O resultado foi... nada de poema. O modelo, ao ver o código Go no meu projeto, interpretou meu pedido como uma instrução para *adicionar uma funcionalidade de haiku ao Speedgrapher*. Ele começou a planejar edições nos meus arquivos Go. Pressionei `ESC` rapidamente para abortar, pois precisava repensar minha estratégia. Essa experiência foi um lembrete marcante de um princípio essencial da engenharia de prompts: a necessidade de equilibrar ambiguidade e contexto. Em muitos dos meus prompts, uso intencionalmente certo grau de ambiguidade para dar ao modelo a flexibilidade de raciocinar e inferir informações. Por exemplo, meu prompt `/review` simplesmente diz para "revisar o artigo no qual estivemos trabalhando". Ele não especifica um nome de arquivo rígido como `DRAFT.md`. Essa ambiguidade é uma ferramenta poderosa em um fluxo de trabalho conversacional, pois permite ao modelo identificar o texto relevante a partir das nossas interações recentes, sem precisar de um caminho de arquivo explícito e rígido. No caso do haiku, porém, a ambiguidade não tinha limites. O contexto principal era um projeto Go, o que levou o modelo a uma conclusão lógica, porém incorreta: a de que eu queria modificar o código. Ele não estava errado; estava apenas fazendo uma inferência plausível. Como eu queria um resultado bastante específico e sem relação com código, minha tarefa nesse caso foi reduzir a ambiguidade fornecendo um contexto muito mais claro sobre a minha intenção. Depois de mais algumas tentativas, cheguei ao seguinte prompt: ```go // The final, working prompt for the haiku command. prompt = fmt.Sprintf("The user wants to have some fun and has requested a haiku about the following topic: %s", topic) ``` Embora eu não tenha certeza se essa é a melhor forma de expressar minha intenção, ela atendeu ao meu propósito, e o modelo passou a produzir haikus consistentemente depois disso. Com o conceito central comprovado, eu estava pronta para construir prompts mais práticos. ## Construindo um kit de ferramentas de escrita O experimento com o haiku confirmou que o conceito central era sólido, então avancei para aplicações mais práticas. Meus arquivos `GEMINI.md` tinham se tornado uma coleção de prompts úteis, mas não portáteis, para tarefas como revisar, traduzir e criar outlines para os meus artigos. Por estarem vinculados a projetos específicos, eu frequentemente esquecia de copiá-los para novos projetos. Um servidor MCP era o próximo passo lógico para tornar essas ferramentas portáteis. Comecei migrando três dos meus prompts mais usados para o Speedgrapher: `interview`, `review` e `localize`. O núcleo desses prompts é um conjunto de "diretrizes editoriais". Por exemplo, a diretriz de localização inclui uma regra para não traduzir termos técnicos, o que ajuda a garantir consistência entre os três idiomas suportados pelo meu blog. Essa abordagem de criar "diretrizes editoriais como código" é uma forma de construir um sistema estruturado que mantém tom de voz e qualidade consistentes, de maneira muito semelhante ao que um linter faz pelo código. Todos os prompts do Speedgrapher foram gerados com a ajuda do Gemini, mas para o prompt `review` adotei uma abordagem ligeiramente diferente. Pedi ao modelo que analisasse meus artigos anteriores e gerasse um conjunto de diretrizes editoriais com base no meu estilo de escrita. O resultado foi um primeiro rascunho sólido, mas é um prompt que estou constantemente refinando. Aqui está a versão atual do prompt, incorporada diretamente do código-fonte do Speedgrapher no GitHub: [View on GitHub: danicat/speedgrapher/internal/prompts/review.go (lines 18-28)](https://github.com/danicat/speedgrapher/blob/main/internal/prompts/review.go#L18-L28) Com os prompts centrais definidos, chegou o momento de trabalhar na automação de outras partes importantes do meu trabalho. ## A legibilidade importa Como escritora técnica, meu maior desafio é encontrar o ponto de equilíbrio ideal entre clareza e complexidade. Se um texto for simples demais, pode parecer infantil. Se for excessivamente complexo, torna-se ilegível. Legibilidade não se resume a tornar as coisas fáceis; trata-se de torná-las envolventes e intelectualmente estimulantes. A boa notícia sobre a legibilidade é que ela pode ser medida. Embora nenhuma métrica seja perfeita, o [Gunning Fog Index](https://en.wikipedia.org/wiki/Gunning_fog_index) é uma excelente ferramenta para se obter uma linha de base. O Gunning Fog Index é um teste de legibilidade que estima os anos de educação formal que uma pessoa necessita para compreender um texto na primeira leitura. Uma pontuação de 12, por exemplo, indica que o texto está no nível de leitura de um estudante do último ano do ensino médio nos Estados Unidos. O índice é calculado com base no seguinte algoritmo: * Selecione um trecho de texto com 100 ou mais palavras. * Calcule o comprimento médio das frases. * Conte a quantidade de palavras "complexas" (palavras com três ou mais sílabas). * Some o comprimento médio das frases à porcentagem de palavras complexas. * Multiplique o resultado por 0,4. Ou, para quem tem inclinação matemática, esse algoritmo se traduz na seguinte equação: \[ 0.4 \times \left[ \left( \frac{\text{palavras}}{\text{frases}} \right) + 100 \left( \frac{\text{palavras complexas}}{\text{palavras}} \right) \right] \] Embora o propósito original do Fog Index seja estimar os anos de escolaridade necessários para compreender o texto, considero pouco prático enquadrá-lo especificamente nesses termos. Por isso, tomei a liberdade de customizá-lo para as minhas próprias necessidades. Primeiro, simplifiquei o cálculo ignorando casos especiais: uma das partes mais complexas do algoritmo é definir o que qualifica uma palavra como complexa (sem trocadilhos). Embora a regra básica considere uma palavra complexa se ela tiver três ou mais sílabas, ela cria casos especiais em que certas terminações de palavras, como *-ing*, *-ed* e *-es*, são ignoradas. Isso causou uma quantidade surpreendente de problemas durante a implementação. Eu não precisava de precisão exata e estava satisfeita em superestimar a complexidade em nome da simplicidade. Para isso, ignorei todos os casos especiais e considerei duas regras básicas para a contagem de sílabas: 1) o número de sílabas em uma palavra é estimado pela quantidade de grupos de vogais, e 2) palavras complexas são palavras que possuem três ou mais sílabas (sem exceções). Também criei um sistema de classificação que desloca o foco dos anos de escolaridade para uma abordagem mais pragmática de legibilidade: | Pontuação | Classificação | Descrição | | :--- | :--- | :--- | | >= 22 | Ilegível | Provavelmente incompreensível para a maioria dos leitores. | | 18-21 | Difícil de ler | Exige esforço significativo, mesmo para especialistas. | | 13-17 | Público profissional | Ideal para leitores com conhecimento especializado. | | 9-12 | Público geral | Claro e acessível para a maior parte dos leitores. | | < 9 | Simplista | Pode ser percebido como infantil ou excessivamente simples. | Com o Gunning Fog Index customizado implementado como uma ferramenta `fog`, o passo final foi criar uma interface amigável para ele. Criei o prompt `/readability`, que invoca a ferramenta `fog` e apresenta os resultados em um formato claro. Isso segue as minhas diretrizes de design para o Speedgrapher: construir ferramentas focadas, de propósito único, e então combiná-las em fluxos de trabalho mais poderosos e fáceis de usar. ## Automatizando o fluxo de trabalho de escrita Os prompts individuais já ajudavam bastante, mas eu ainda tinha muito a automatizar até alcançar o fluxo de trabalho dos meus sonhos. Nas iterações seguintes, testei os prompts no dia a dia e mapeei as lacunas do processo para propor novos prompts e/ou ajustar os já existentes. Aqui estão os prompts que utilizo atualmente: **Fluxo principal** * `/interview`: Entrevista o autor para coletar material para um artigo. Costuma ser o ponto de partida de uma sessão de escrita. * `/outline`: Gera um outline estruturado a partir do rascunho atual, conceito ou transcrição da entrevista. * `/voice`: Analisa a voz e o tom da escrita da pessoa usuária para replicá-los no texto gerado. * `/expand`: Expande um outline ou rascunho de trabalho em um artigo mais detalhado. Também pode ser usado com o argumento `hint` para fazer uma expansão focada de um parágrafo ou seção específica. * `/review`: Revisa o artigo atualmente em produção com base nas diretrizes editoriais. * `/readability`: Analisa o último texto gerado quanto à legibilidade usando o Gunning Fog Index. * `/localize`: Traduz o artigo atualmente em produção para um idioma de destino. * `/publish`: Publica a versão final do artigo. **Opcional** * `/context`: Carrega o artigo em andamento no contexto para comandos subsequentes. É usado para "lembrar" o modelo sobre o rascunho atual se necessário, e frequentemente executado antes de comandos como `/readability` ou `/review`, que operam sobre o texto completo. * `/reflect`: Analisa a sessão atual e propõe melhorias para o processo de escrita. Muito útil para aperfeiçoar prompts e diretrizes editoriais. O objetivo era evoluir de uma coleção de comandos úteis para um processo único e otimizado, capaz de guiar um artigo desde uma ideia simples até uma publicação multilíngue e refinada. O diagrama abaixo é uma representação simplificada do meu fluxo de trabalho: ```mermaid flowchart TD A[Ideia] -->|/interview| B[Transcrição da Entrevista] B -->|/outline & /voice| C[Outline Estruturado] C -->|/expand| D[Rascunho do Artigo] D -->|/review & /readability| E[Rascunho Revisado] E -->|/localize| F[Versões Traduzidas] F -->|/publish| G[Artigo Publicado] ``` O processo começa com um `/interview` para desenvolver os conceitos centrais de uma ideia. A transcrição resultante é então transformada em um plano estruturado via `/outline` e alinhada ao meu estilo pessoal de escrita com o `/voice`. Com essa base sólida, entro em um ciclo iterativo usando `/expand` para desenvolver o rascunho, e `/review` e `/readability` para refiná-lo. Quando o artigo é aprovado, utilizo `/localize` para criar versões em outros idiomas e `/publish` para concluir o processo. O prompt opcional `/reflect` pode ser usado para analisar a sessão e gerar notas para melhorias futuras, criando um ciclo de refinamento contínuo. ## Conclusão Assim como usamos linters e testes para trazer estrutura ao nosso código, podemos aplicar princípios semelhantes aos nossos fluxos de trabalho criativos. O processo de escrita envolve muitas tarefas repetitivas que podem ser automatizadas. Ao construir um toolkit pessoal de prompts, podemos delegar o boilerplate e focar nas ideias centrais do nosso trabalho. Esse é o verdadeiro valor de uma ferramenta como o Speedgrapher: "vibe writing" não é sobre substituir quem escreve, mas sobre aumentar o processo de escrita. Ao adicionar um servidor MCP ao fluxo, ganhamos uma camada útil de estrutura em um fluxo de trabalho que muitas vezes pode ser desorganizado, garantindo que as melhores práticas sejam seguidas. O mesmo pode ser aplicado a qualquer processo assistido por IA: ao tratar seus próprios prompts como ativos reutilizáveis e portáteis, você cria um sistema que evolui junto com o seu processo, permitindo que você se concentre nos aspectos criativos do seu trabalho, um prompt de cada vez. ## O que vem a seguir? A jornada com o Speedgrapher está longe de terminar. Embora o toolkit atual seja focado em texto, o próximo passo lógico é abraçar a multimodalidade. Estou explorando como integrar ferramentas para geração de hero images, criação de diagramas mais sofisticados a partir do texto e até sugestões de otimização de layout. O objetivo é continuar construindo um toolkit pessoal que lide com mais tarefas paralelas à escrita, liberando-me para focar no conteúdo em si. ## Recursos * **[Projeto Speedgrapher](https://github.com/danicat/speedgrapher):** O código-fonte do servidor MCP discutido neste artigo. * **[Construindo o GoDoctor: Um Servidor MCP com Gemini CLI e Go](https://danicat.dev/pt-br/posts/20250729-how-to-build-an-mcp-server-with-gemini-cli-and-go/):** O artigo anterior que inspirou esta jornada. * **[Model Context Protocol (MCP)](https://modelcontextprotocol.io/):** O site oficial do protocolo. * **[Anúncio da Gemini CLI](https://blog.google/technology/developers/introducing-gemini-cli-open-source-ai-agent/):** O post no blog que anunciou o suporte a slash commands customizados. * **[Gunning Fog Index](https://en.wikipedia.org/wiki/Gunning_fog_index):** Saiba mais sobre a métrica de legibilidade. --- # Construindo o GoDoctor: Como Criar um Servidor MCP com Gemini CLI e Go - **Markdown URL**: https://danicat.dev/pt-br/posts/how-to-build-an-mcp-server-with-gemini-cli-and-go/index.md - **HTML URL**: https://danicat.dev/pt-br/posts/how-to-build-an-mcp-server-with-gemini-cli-and-go/ - **Date**: 2025-07-29 - **Categories**: Agent Development - **Tags**: ai, codelab, gemini-cli, golang, mcp, tutorial - **Summary**: Tutorial passo a passo construindo o GoDoctor, um servidor MCP em Go com o Gemini CLI. Aborda transportes stdio e HTTP, handshake e deploy no Cloud Run. > **Nota:** Este artigo foi escrito para o Gemini CLI, que foi descontinuado e substituído pelo **Google Antigravity 2.0**. Para saber mais sobre a nova Antigravity CLI (`agy`), o SDK e o ecossistema Antigravity mais amplo, confira [O Guia do Mochileiro para o Antigravity 2.0](https://danicat.dev/pt-br/posts/20260521-the-hitchhikers-guide-to-antigravity-2-0/). ## Introdução Como muitos de vocês, tenho mergulhado fundo no desenvolvimento assistido por IA. A jornada é muitas vezes uma montanha-russa de momentos "uau" e obstáculos frustrantes. Esta é a história de uma dessas jornadas — uma história que começou comigo tentando construir uma coisa, me desviando completamente por causa de um problema frustrante e terminando com uma ferramenta que melhorou fundamentalmente meu fluxo de trabalho assistido por IA. Meu objetivo original era construir um servidor de Model Context Protocol (MCP) para o [osquery](https://www.osquery.io/), uma ferramenta que permite consultar o estado de uma máquina usando SQL. Eu estava empolgada para usar o Gemini CLI para me ajudar a escrever o código em Go. No entanto, rapidamente bati de frente com uma parede. O código em Go que o agente produzia frequentemente não era idiomático. Ele cometia erros de iniciante, criava abstrações excessivas e com frequência "alucinava" APIs inteiras que simplesmente não existiam. Minha suspeita era de que o modelo subjacente não havia sido treinado no mais recente [Go SDK for MCP](https://github.com/modelcontextprotocol/go-sdk), então ele preferia inventar respostas a admitir que não sabia. Essa experiência me levou a uma constatação fundamental: em vez de lutar contra a ferramenta, eu poderia *ensiná-la*. Decidi pausar meu projeto com o osquery e embarcar em uma "side quest": construir um servidor MCP dedicado cujo único propósito fosse ser um especialista em desenvolvimento Go. Esse projeto paralelo, que eventualmente chamei de [GoDoctor](https://github.com/danicat/godoctor), forneceria as ferramentas de que o Gemini CLI precisava para escrever um código Go muito melhor. Neste post, vou guiar vocês pela história da construção do GoDoctor. Este texto é menos um tutorial tradicional e mais uma jornada "orientada por prompts". Vamos focar em como traduzir os requisitos do projeto em prompts eficazes e guiar a IA pelos detalhes de implementação, aprendendo com os erros inevitáveis ao longo do caminho. ## Preparando o Terreno: O `GEMINI.md` Antes de escrever uma única linha de código, o primeiro passo foi estabelecer as regras básicas. Embora o `GEMINI.md` seja um arquivo específico do Gemini CLI, a prática de criar um arquivo de contexto é comum em muitos agentes de programação com IA (por exemplo, o Jules usa `AGENTS.md` e o Claude usa `CLAUDE.md`). Na verdade, existe um esforço emergente para padronizar isso com um arquivo chamado [`AGENT.md`](https://ampcode.com/AGENT.md). Esse arquivo é crucial porque fornece à IA uma compreensão fundamental dos padrões do seu projeto e das suas expectativas em relação ao comportamento dela. Como este projeto era novinho em folha, eu ainda não tinha nenhum detalhe arquitetural específico para compartilhar. Por isso, comecei com um conjunto genérico de diretrizes focadas na criação de código Go idiomático e de alta qualidade. Conforme o projeto evolui, é comum adicionar instruções mais específicas sobre a estrutura do projeto, comandos de build ou bibliotecas essenciais. Para ver um exemplo de arquivo mais específico de projeto, você pode conferir o `GEMINI.md` que uso no meu [projeto `testquery`](https://github.com/danicat/testquery/blob/main/GEMINI.md). Aqui está o `GEMINI.md` inicial que serviu como a constituição para a IA nesta jornada: ```markdown # Go Development Guidelines All code contributed to this project must adhere to the following principles. ## 1. Formatting All Go code **must** be formatted with `gofmt` before being submitted. ## 2. Naming Conventions - **Packages:** Use short, concise, all-lowercase names. - **Variables, Functions, and Methods:** Use `camelCase` for unexported identifiers and `PascalCase` for exported identifiers. - **Interfaces:** Name interfaces for what they do (e.g., `io.Reader`), not with a prefix like `I`. ## 3. Error Handling - Errors are values. Do not discard them. - Handle errors explicitly using the `if err != nil` pattern. - Provide context to errors using `fmt.Errorf("context: %w", err)`. ## 4. Simplicity and Clarity - "Clear is better than clever." Write code that is easy to understand. - Avoid unnecessary complexity and abstractions. - Prefer returning concrete types, not interfaces. ## 5. Documentation - All exported identifiers (`PascalCase`) **must** have a doc comment. - Comments should explain the *why*, not the *what*. # Agent Guidelines - **Reading URLs:** ALWAYS read URLs provided by the user. They are not optional. ``` Esse arquivo estabelece uma base de qualidade e estilo desde o início. ## Entendendo o Model Context Protocol (MCP) No coração deste projeto está o Model Context Protocol (MCP). Algumas pessoas o descrevem como um "padrão USB para ferramentas de LLM", mas gosto de pensar de outra forma: **o que o HTTP e o REST fizeram pela padronização de APIs web, o MCP está fazendo pelas ferramentas de LLM.** Da mesma forma que o REST forneceu uma arquitetura previsível que desbloqueou um ecossistema massivo de serviços web, o MCP está trazendo uma linguagem comum fundamental para o universo dos agentes de IA. Trata-se de um protocolo baseado em JSON-RPC que estabelece um terreno comum, permitindo que qualquer agente que "fale" MCP descubra e use qualquer ferramenta compatível sem a necessidade de uma integração personalizada e pontual. O protocolo define diferentes maneiras de comunicação entre o agente e o servidor de ferramentas, conhecidas como transportes (transports). Os dois mais comuns são: * **HTTP:** O modelo familiar de requisição/resposta (request/response), ideal para ferramentas implantadas como serviços remotos (por exemplo, no [Cloud Run](https://cloud.google.com/run?utm_campaign=CDR_0x72884f69_default_b421852297&utm_medium=external&utm_source=blog)). * **stdio:** Um transporte robusto que utiliza a entrada e saída padrão (standard input/output), perfeito para executar uma ferramenta como um processo local na sua máquina. Com o transporte `stdio`, o agente e o servidor trocam uma série de mensagens JSON-RPC. O processo se inicia com um handshake de três etapas fundamental para estabelecer a conexão. Somente após a conclusão desse handshake o cliente pode começar a fazer chamadas de ferramentas. A sequência funciona assim: ![Diagrama de sequência do stdio do MCP](images/mcp-stdio-sequence-diagram.png) *

Figura 1: Diagrama de sequência do transporte stdio da documentação oficial do MCP (2025-06-18).

* Aqui está como são as mensagens JSON desse handshake inicial, com base na especificação oficial: **1. Cliente → Servidor: Requisição `initialize`** O cliente inicia a conversa enviando uma requisição `initialize`. ```json { "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "protocolVersion": "2025-06-18", "clientInfo": { "name": "Gemini CLI", "version": "1.0.0" } } } ``` **2. Servidor → Cliente: Resultado de `initialize`** O servidor responde com o resultado, confirmando a versão do protocolo e anunciando suas capacidades e informações. Esta é a resposta real do binário do `godoctor`: ```json { "jsonrpc": "2.0", "id": 1, "result": { "capabilities": { "completions": {}, "logging": {}, "tools": { "listChanged": true } }, "protocolVersion": "2025-06-18", "serverInfo": { "name": "godoctor", "version": "0.2.0" } } } ``` **3. Cliente → Servidor: Notificação `initialized`** Por fim, o cliente confirma que a configuração foi concluída enviando uma notificação `initialized`. Observe que isso é uma notificação, portanto não possui o campo `id`, e o método possui namespace. ```json { "jsonrpc": "2.0", "method": "notifications/initialized", "params": {} } ``` Uma vez concluída essa troca, a sessão está estabelecida e o cliente pode prosseguir com as chamadas de ferramentas. Por exemplo, para solicitar ao servidor a lista de ferramentas disponíveis, você pode enviar uma requisição `tools/list`. O segredo é que todas as três mensagens do handshake devem ser enviadas na ordem correta antes dessa requisição. Você pode conferir a sequência completa em ação com este script shell: ```bash #!/bin/bash ( echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","clientInfo":{"name":"Manual Test Client","version":"1.0.0"}}}'; echo '{"jsonrpc":"2.0","method":"notifications/initialized","params":{}}'; echo '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}'; ) | godoctor ``` Passar esse script via pipe para o binário do `godoctor` produz primeiro o resultado de `initialize`, seguido pelo resultado de `tools/list`, que lista corretamente todas as ferramentas do GoDoctor. Entender esse fluxo obrigatório em três etapas foi a chave para resolver meu maior obstáculo inicial, que descreverei na próxima seção. Para quem está conhecendo o MCP agora, recomendo fortemente a leitura da documentação oficial. Os dois documentos mais cruciais para mim foram as páginas sobre o [ciclo de vida de cliente/servidor](https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle) e a [camada de transporte](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports). (Ou, se estiver com preguiça, passe essas URLs para a CLI e deixe que ela leia para você =^_^=) ## A Primeira Conquista: Um Agente que Lê a Documentação Meu primeiro objetivo era resolver o problema de alucinação de APIs. Como acontece em muitas primeiras tentativas de elaboração de prompts, meu pedido inicial foi simples e um pouco vago: > "Crie um servidor MCP em Go que tenha uma ferramenta chamada `godoc`. Esta ferramenta deve receber o nome de um pacote e um nome de símbolo opcional e executar o comando `go doc`." Os resultados não foram nada bons. O agente perdeu muito tempo tentando descobrir quais ferramentas usar e qual protocolo seria o melhor. Até mesmo a sigla "MCP" não era um conceito óbvio para ele; com frequência, ele deduzia que ela significava outras coisas até que esclareci que se tratava de "Model Context Protocol". Ele ficou preso em loops chamando o Google Search e o WebFetch, testando SDKs diferentes, falhando em produzir um exemplo funcional e então mudando para outro SDK, várias e várias vezes. É aqui que o trabalho real de "vibe coding" começa. Trata-se de um processo iterativo de refinamento das instruções. Após algumas horas de tentativa e erro, cheguei a um prompt muito mais eficaz. Aprendi que fornecer recursos específicos e de alta qualidade era o grande segredo. Aqui está o prompt final, muito melhorado: > Sua tarefa é criar um servidor de Model Context Protocol (MCP) para expor o comando go doc, dando aos LLMs a capacidade de consultar a documentação de Go. A ferramenta deve se chamar go-doc e deve receber dois argumentos: package_path (obrigatório) e symbol_name (opcional). Para a parte de documentação, use o comando de shell `go doc`. Para a implementação do MCP, você deve usar o SDK oficial de Go para MCP e escrever um servidor MCP pronto para produção que se comunique por meio de um transporte stdio. Você também deve criar um cliente CLI simples para me permitir testar o servidor. > > Leia estas referências para coletar informações sobre a tecnologia e a estrutura do projeto antes de escrever qualquer código: > - https://github.com/modelcontextprotocol/go-sdk > - https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle > - https://go.dev/doc/modules/layout Esse prompt é superior por vários motivos: ele especifica o SDK exato a ser utilizado, dita o transporte (`stdio`) e, acima de tudo, entrega uma lista de leitura ao agente. Ao fornecer links para o código-fonte do SDK e para a especificação do MCP, reduzi drasticamente a tendência do agente de alucinar. Mesmo com esse prompt aprimorado, o caminho não foi tranquilo. O maior obstáculo surgiu quando tentei usar o transporte `stdio`. Minhas chamadas de ferramentas falhavam sistematicamente com um erro enigmático: `server initialisation is not complete`. Depois de muito debugging doloroso, descobri que o problema não estava no código do meu servidor. A questão era que o transporte `stdio` do MCP exige o handshake específico de três etapas que descrevi acima. Meu cliente estava tentando chamar a ferramenta antes da conclusão do handshake. Essa experiência me ensinou uma lição valiosa: ao criar ferramentas para IA, você não está apenas depurando código, você está depurando o próprio protocolo de conversação. Com o servidor rodando e falando o protocolo corretamente, a próxima peça do quebra-cabeça era apresentá-lo ao Gemini CLI. Isso é gerenciado por um arquivo `.gemini/settings.json` na raiz do projeto, que indica à CLI quais ferramentas carregar. Adicionei a seguinte configuração a ele: ```json { "mcpServers": { "godoctor": { "command": "./bin/godoctor" } } } ``` Com isso configurado, sempre que eu iniciava o Gemini CLI nesse diretório, ele inicializava automaticamente meu servidor `godoctor` em segundo plano e disponibilizava suas ferramentas para o agente. ## Criando um Loop de Feedback com um Revisor de Código de IA Com uma ferramenta `godoc` funcional, o próximo passo lógico era ensinar o agente não apenas a ler sobre código, mas a raciocinar sobre sua qualidade. Isso nos levou à ferramenta `code_review`. Dessa vez, a experiência foi muito mais suave, como resultado direto do trabalho prévio que já havíamos feito. Meu prompt focou no objetivo, e não na implementação: > Eu quero adicionar uma nova ferramenta ao meu projeto chamada code_review. Esta ferramenta deve usar a Gemini API para analisar código Go e fornecer uma lista de melhorias em formato json de acordo com as melhores práticas aceitas pela comunidade Go. A ferramenta deve receber o conteúdo do código Go e uma dica opcional como entrada... > > Use este SDK para chamar o Gemini: https://github.com/googleapis/go-genai O agente ainda precisava aprender o Go SDK de `genai`, mas dessa vez ele contava com a nossa ferramenta `godoc` na sua caixa de ferramentas. Eu conseguia vê-lo usando a ferramenta para consultar a documentação do SDK, corrigir seus próprios erros e aprender em tempo real. O processo ainda foi iterativo, mas muito mais rápido e eficiente. O resultado mais importante não foi apenas a ferramenta em si, mas a nova capacidade que ela desbloqueou. Pela primeira vez, eu podia usar a ferramenta para revisar o próprio código que ela gerava, destravando outro patamar de desenvolvimento orientado por IA. **Eu havia criado um loop de feedback positivo.** Meu fluxo de trabalho agora contava com um novo passo poderoso. Depois que o agente gerava um trecho de código, eu podia pedir imediatamente que ele fizesse uma crítica ao próprio trabalho: > "Agora, use a ferramenta `code_review` no código que você acabou de escrever e aplique as sugestões." O agente então analisava a própria saída e a refatorava com base no feedback gerado pela IA. Esse é o verdadeiro poder de construir ferramentas para IA: você não está apenas automatizando tarefas, está criando sistemas para autoaprimoramento. ## O Capítulo Final: Implantando na Nuvem > **Warning:** Se você implantar seu próprio servidor MCP no Cloud Run, certifique-se de configurar a autenticação adequada. **Não faça deploy de um servidor acessível publicamente**, especialmente se ele usar uma chave de API do Gemini. Um endpoint público pode ser explorado por agentes mal-intencionados, potencialmente gerando uma fatura de nuvem gigantesca e inesperada. Uma ferramenta local rodando via `stdio` é excelente para uso pessoal, mas o propósito do Model Context Protocol é criar um ecossistema de ferramentas compartilhadas e localizáveis. A próxima fase dessa "side quest" foi transformar o GoDoctor de um binário local no meu notebook em um serviço web escalável usando o [Google Cloud Run](https://cloud.google.com/run?utm_campaign=CDR_0x72884f69_default_b421852297&utm_medium=external&utm_source=blog). Isso significava ensinar ao agente duas novas habilidades de desenvolvimento em nuvem: como conteinerizar uma aplicação e como implantá-la. Primeiro, precisávamos migrar do transporte `stdio` para o `HTTP`. Meu prompt foi direto ao ponto, aproveitando o trabalho que já havíamos feito: > "Chegou a hora de levar nosso servidor para a web. Por favor, refatore o servidor MCP do transporte `stdio` para usar o transporte `streamable HTTP`." Com o servidor agora falando HTTP, o próximo passo era empacotá-lo para a nuvem. Pedi ao agente para criar um `Dockerfile` multi-stage pronto para produção, que é a forma padrão de criar imagens de contêiner leves e seguras. > "Por favor, crie um Dockerfile multi-stage que compile o binário Go e o copie para uma imagem mínima `golang:1.24-alpine`." Com o `Dockerfile` pronto, era hora do deploy. Esse é o momento em que a prova de conceito local se transforma em uma peça real de infraestrutura na nuvem. > "Agora, por favor, faça o deploy dessa imagem no Cloud Run. Faça o deploy em `us-central1` e use o projeto atualmente configurado no ambiente. Quando terminar, me dê a URL que posso usar para chamar a ferramenta MCP." O agente forneceu os comandos `gcloud` corretos e, em poucos minutos, o GoDoctor estava no ar na internet. Para finalizar a configuração, precisei avisar ao meu Gemini CLI local sobre o servidor remoto. Isso significou atualizar o arquivo `.gemini/settings.json`, trocando o `command` local pelo `httpUrl` remoto: ```json { "mcpServers": { "godoctor": { "httpUrl": "https://.run.app" } } } ``` E, simples assim, minha CLI estava utilizando uma ferramenta implantada e rodando na nuvem. Esse foi o momento em que a prova de conceito pareceu verdadeiramente completa. O processo iterativo de guiar o agente havia compensado todo o esforço, levando uma ideia por todo o ciclo de vida de uma aplicação moderna — de um conceito local a um serviço cloud-native escalável. Dito isso, para o meu trabalho no dia a dia, continuo usando a versão `stdio` — para uma equipe de uma pessoa só, fazer deploy no Cloud Run é um exagero. ## Minhas Principais Lições do Vibe Coding com o GoDoctor Esta jornada foi menos sobre escrever código e mais sobre aprender a colaborar de maneira eficaz com uma IA. Minha maior lição foi mudar minha mentalidade de "programadora" para "professora" ou "piloto". Aqui estão algumas das lições mais importantes que aprendi: * **Você é o piloto.** A IA às vezes vai propor ações com as quais você não concorda. Não tenha medo de apertar `ESC` para cancelar e fornecer um novo prompt para guiá-la na direção certa. * **Mantenha a IA por dentro.** O ideal é deixar o agente executar todo o trabalho, mas às vezes uma edição manual é necessária. Quando você mesma altera o código, o contexto da IA fica desatualizado. Lembre-se de avisar o que você mudou para que ela continue sincronizada com a base de código. * **Quando tudo falhar, desligue e ligue de novo.** No caso (nada raro) de a IA travar completamente, a solução clássica de TI faz milagres. No Gemini CLI, isso significa usar o comando `/compress` para enxugar o histórico da conversa ou, em situações extremas, reiniciar a CLI para recomeçar com um contexto limpo. Ao fornecer ao agente o contexto correto e as ferramentas certas, ele se tornou um parceiro muito mais capaz. A jornada deixou de ser sobre mim tentando fazer com que o código fosse escrito, e passou a ser sobre a construção de um sistema capaz de aprender e se aprimorar continuamente. ## O Que Vem a Seguir? A jornada com o GoDoctor está longe de terminar. Ele ainda é um projeto experimental, e aprendo mais a cada nova ferramenta e interação. Meu objetivo é continuar evoluindo o projeto para que ele se torne um assistente de desenvolvimento genuinamente útil para desenvolvedores Go em qualquer lugar. Se você quiser vivenciar essa jornada e construir seu próprio servidor MCP do zero, eu criei um workshop prático para guiar você ao longo do processo. Confira o codelab **[Build an MCP Server with Go and Gemini CLI](https://codelabs.developers.google.com/cloud-gemini-cli-mcp-go)**. Para quem tiver interesse em como esses conceitos estão sendo aplicados na toolchain oficial do Go, recomendo fortemente a leitura sobre o servidor MCP do `gopls`, que compartilha de muitos desses mesmos objetivos. Você pode encontrar mais informações no [site oficial de documentação de Go](https://tip.golang.org/gopls/features/mcp). ## Recursos e Links Aqui estão alguns dos principais recursos que mencionei ao longo deste artigo. Espero que sejam tão úteis para você quanto foram para mim. * **[Repositório do Projeto GoDoctor](https://github.com/danicat/godoctor):** O código-fonte completo da ferramenta que discutimos. * **[Página Oficial do Model Context Protocol](https://modelcontextprotocol.io/):** O melhor ponto de partida para aprender sobre o MCP. * **[Especificação do MCP (2025-06-18)](https://modelcontextprotocol.io/specification/2025-06-18):** A especificação técnica completa. * **[Documentação do Ciclo de Vida do MCP](https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle):** Uma leitura essencial para compreender o handshake entre cliente e servidor. * **[Documentação de Transporte do MCP](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports):** Essencial para entender as diferenças entre os transportes `stdio` e `http`. --- # Proatividade considerada prejudicial?! Um mergulho profundo no system prompt do Gemini CLI - **Markdown URL**: https://danicat.dev/pt-br/posts/gemini-cli-system-prompt/index.md - **HTML URL**: https://danicat.dev/pt-br/posts/gemini-cli-system-prompt/ - **Date**: 2025-07-14 - **Categories**: Agentic Coding - **Tags**: gemini-cli, tutorial, vibe-coding - **Summary**: Guia passo a passo para controlar a proatividade do Gemini CLI usando arquivos de contexto GEMINI.md e a variável GEMINI_SYSTEM_MD para se adequar ao seu estilo de codificação. > **Nota:** Este artigo foi escrito para o Gemini CLI, que foi descontinuado e substituído pelo **Google Antigravity 2.0**. Para saber mais sobre a nova Antigravity CLI (`agy`), o SDK e o ecossistema Antigravity atualizado, confira [O Guia do Mochileiro para o Antigravity 2.0](https://danicat.dev/pt-br/posts/20260521-the-hitchhikers-guide-to-antigravity-2-0/). ## Introdução Muitos de vocês já devem estar familiarizados com o [Gemini CLI](https://cloud.google.com/gemini/docs/codeassist/gemini-cli?utm_campaign=CDR_0x72884f69_default_b432031389&utm_medium=external&utm_source=blog) a esta altura, mas caso ainda não estejam, confiram o [post oficial de lançamento](https://blog.google/technology/developers/introducing-gemini-cli-open-source-ai-agent/) para uma visão geral rápida. Escrevi sobre como o incluí no meu fluxo de trabalho no meu post anterior ["Um Fluxo de Trabalho de Desenvolvimento Moderno para o Mundo Habilitado por IA"](https://danicat.dev/pt-br/posts/20250714-developer-workflow/), mas desta vez quero explorar algo ligeiramente diferente. Se você já usa o CLI há algum tempo, deve ter notado que ele é muito "proativo", inferindo os próximos passos com base até nos prompts mais ambíguos e imediatamente partindo para a execução de ações com base nessas inferências. A intenção é dar à conversa um fluxo mais humano. Por exemplo, se você instrui o modelo a escrever alguns testes, mas ele esqueceu de atualizar o README com as instruções para rodar esses testes, você pode querer fazer uma pergunta de acompanhamento como "Você não deveria atualizar o README para adicionar as instruções de execução dos testes?", e o CLI vai assumir que a pergunta é retórica e partir direto para o comando de atualizar o arquivo README. Embora esse nível de proatividade possa ser inofensivo ou até desejável na média dos casos, na minha experiência ele tende a atrapalhar meu fluxo de trabalho com mais frequência do que o contrário. Um exemplo típico: depois de fazer um "vibe coding" de algum código, peço ao CLI para esclarecer "por que você adicionou esse arquivo @x?", apenas para ele assumir que eu não quero que o arquivo exista e *proativamente* deletar o arquivo sem qualquer explicação. Eu geralmente fico profundamente irritada com esse tipo de interação, porque na maioria das vezes estou fazendo uma pergunta genuína e esperando uma resposta. Personalizar o CLI para se adequar ao seu próprio estilo de comunicação é essencial para garantir que as ferramentas atendam às suas necessidades. Ninguém quer ficar brigando com uma IA enquanto tenta fazer um trabalho produtivo, e esse tem sido um motivo frequente que as pessoas me dão para descartar ferramentas de IA em favor de seus métodos tradicionais com IDEs e autocomplete. Para fazer o Gemini CLI se comportar de uma maneira mais produtiva para você, nas próximas duas seções vamos explorar duas formas de customizar as respostas do CLI: o arquivo GEMINI.md e as system instructions. ## Customizando o Gemini CLI com GEMINI.md O GEMINI.md é um arquivo usado para fornecer contexto adicional para o CLI. Assim que você inicia o CLI pelo seu terminal, ele procura um arquivo GEMINI.md na pasta atual e em todas as suas subpastas. Esses arquivos podem ser usados para todo tipo de coisa, mas uma estrutura de trabalho típica é usar o arquivo GEMINI.md raiz para explicar seu projeto ao CLI — desde o seu propósito até a organização das pastas e instruções essenciais como build, testes, etc. É, na verdade, muito semelhante a um bom README.md, com a única diferença de que esse arquivo é escrito pensando na IA, sendo mais voltado a instruções e prompts. O arquivo GEMINI.md de nível superior também é um ótimo lugar para passar instruções operacionais para o CLI. Diretrizes como "faça um plano e peça confirmação antes de implementar qualquer tarefa" ou "sempre faça commit dos passos intermediários usando git" podem ser adicionadas a esse arquivo para garantir um fluxo de trabalho mais consistente. Aqui está um ótimo exemplo de arquivo que define um processo para o CLI seguir (agradecimento especial a Ryan J. Salva por compartilhar isso): [GitHub Gist: ryanjsalva/0a7f6782b8988e760b88f1635ea55f2e](https://gist.github.com/ryanjsalva/0a7f6782b8988e760b88f1635ea55f2e) Por outro lado, arquivos GEMINI.md aninhados podem ser úteis para explicar partes específicas da base de código. Se você tem um monorepo com código de frontend e backend no mesmo lugar, pode ter um GEMINI.md customizado para cada um desses componentes. Ou se estiver escrevendo um programa em Go com vários pacotes internos, onde cada um tem suas particularidades que você deseja que o CLI respeite. Qualquer que seja o seu caso de uso, ter múltiplos arquivos GEMINI.md ajuda a alcançar um controle granular sobre o contexto para tarefas específicas. Nota: Da mesma forma que o Gemini CLI usa o GEMINI.md como arquivo de contexto, outras ferramentas de IA como [Claude](https://www.anthropic.com/product/claude) e [Jules](https://jules.google) têm seus próprios arquivos markdown (CLAUDE.md e AGENTS.md, respectivamente). Se você não gostar do nome GEMINI.md ou quiser garantir que todas as ferramentas usem o mesmo arquivo, é possível configurar o nome do arquivo de contexto usando a propriedade `contextFileName` no `settings.json`: ```json { "contextFileName": "AGENTS.md" } ``` ## Mantendo os arquivos GEMINI.md A principal reclamação que ouço sobre o(s) arquivo(s) GEMINI.md é que se trata de mais um arquivo para manter. A boa notícia é que você não precisa mantê-lo manualmente. Uma prática que adoto após "parear" com o Gemini em uma tarefa de programação por algum tempo — especialmente naquelas sessões em que muitos problemas ou mal-entendidos aconteceram — é pedir ao Gemini para resumir os aprendizados, de modo que os mesmos problemas não se repitam no futuro, e aplicar esses aprendizados como novas instruções ou ajustes no arquivo GEMINI.md. Dessa forma, a tendência é que o modelo evolua junto com as suas experiências e preferências pessoais, mesmo que leve alguns dias para afiná-lo. ## A opção nuclear: sobrescrevendo as system instructions Embora um ou mais arquivos GEMINI.md sejam o caminho recomendado para customizar o CLI, às vezes precisamos apelar para a opção nuclear porque as [system instructions](https://cloud.google.com/vertex-ai/generative/docs/concepts/system-instructions?utm_campaign=CDR_0x72884f69_default_b432031389&utm_medium=external&utm_source=blog) e os nossos arquivos GEMINI.md entram em conflito direto. Como mencionei na introdução deste artigo, fico particularmente irritada quando o modelo tenta ser "proativo demais" e deduz intenções nos meus prompts que eu jamais pedi. Deletar um arquivo que eu não queria que fosse excluído, emendar um commit quando deveria ter criado um novo, fazer uma "limpeza" no repositório quando tenho trabalho não commitado em que gastei horas... essas foram algumas das queimaduras da vida real que sofri nas últimas semanas devido a essa proatividade excessiva. Tentei ajustar o GEMINI.md para forçá-lo a responder de forma estritamente literal aos meus prompts, com pouco sucesso, o que acabou me levando a mergulhar de cabeça na toca do coelho das system instructions. Minha hipótese era de que havia algo com prioridade superior ao GEMINI.md interferindo no comportamento. Felizmente o Gemini CLI é open source, então pude simplesmente inspecionar o código-fonte e encontrar o prompt. Aqui está um trecho do que encontrei: ```markdown You are an interactive CLI agent specializing in software engineering tasks. Your primary goal is to help users safely and efficiently, adhering strictly to the following instructions and utilizing your available tools. ## Core Mandates - Conventions: Rigorously adhere to existing project conventions when reading or modifying code. Analyze surrounding code, tests, and configuration first. - Libraries/Frameworks: NEVER assume a library/framework is available or appropriate. Verify its established usage within the project (check imports, configuration files like 'package.json', 'Cargo.toml', 'requirements.txt', 'build.gradle', etc., or observe neighboring files) before employing it. - Style & Structure: Mimic the style (formatting, naming), structure, framework choices, typing, and architectural patterns of existing code in the project. - Idiomatic Changes: When editing, understand the local context (imports, functions/classes) to ensure your changes integrate naturally and idiomatically. - Comments: Add code comments sparingly. Focus on why something is done, especially for complex logic, rather than what is done. Only add high-value comments if necessary for clarity or if requested by the user. Do not edit comments that are separate from the code you are changing. NEVER talk to the user or describe your changes through comments. - Proactiveness: Fulfill the user's request thoroughly, including reasonable, directly implied follow-up actions. - Confirm Ambiguity/Expansion: Do not take significant actions beyond the clear scope of the request without confirming with the user. If asked how to do something, explain first, don't just do it. - Explaining Changes: After completing a code modification or file operation do not provide summaries unless asked. - Path Construction: Before using any file system tool (e.g., ${ReadFileTool.Name}' or '${WriteFileTool.Name}'), you must construct the full absolute path for the `file_path` argument. Always combine the absolute path of the project's root directory with the file's path relative to the root. For example, if the project root is /path/to/project/ and the file is foo/bar/baz.txt, the final path you must use is /path/to/project/foo/bar/baz.txt. If the user provides a relative path, you must resolve it against the root directory to create an absolute path. - Do Not revert changes: Do not revert changes to the codebase unless asked to do so by the user. Only revert changes made by you if they have resulted in an error or if the user has explicitly asked you to revert the changes. ``` O system prompt é enorme. Nesta prévia estamos mostrando apenas as primeiras doze linhas, mas ele vai muito além, recomendando tecnologias para os casos de uso mais comuns e tudo mais (você pode ver o prompt completo no GitHub clicando no link acima). Isso faz todo o sentido para um CLI que precisa atender a tantos cenários diferentes, mas pode não ser o ideal para o nosso projeto específico. O que mais me incomoda está na linha 49: ```markdown - Proactiveness: Fulfill the user's request thoroughly, including reasonable, directly implied follow-up actions. ``` Acredito que essa linha inteira seja a raiz de 80% dos meus problemas, porque na maioria das vezes eu só quero que uma pergunta seja tratada como uma simples pergunta. A dúvida agora é: como nos livramos disso? Poderíamos abrir um PR para remover essa linha, mas talvez ela seja útil para outras pessoas. Eu poderia criar um fork do projeto e fazer a minha própria Daniela CLI, mas isso também não seria muito prático. Felizmente, lendo o código, encontrei variáveis de ambiente não documentadas que ajudam enormemente nesse processo: `GEMINI_SYSTEM_MD` e `GEMINI_WRITE_SYSTEM_MD`: 1. `GEMINI_SYSTEM_MD` permite sobrescrever o system prompt padrão com um arquivo markdown customizado. Uso: 1. `GEMINI_SYSTEM_MD=SYSTEM.md`: lê o system prompt a partir do arquivo customizado `SYSTEM.md`. 2. `GEMINI_SYSTEM_MD=1`: lê o system prompt de `~/.gemini/system.md`. 2. `GEMINI_SYSTEM_MD=0` ou `GEMINI_SYSTEM_MD=""`: o system prompt é [construído durante o boot](https://github.com/google-gemini/gemini-cli/blob/main/packages/core/src/core/prompts.ts) (Padrão). 2. `GEMINI_WRITE_SYSTEM_MD` permite salvar o system prompt em disco no caminho especificado. Uso: 1. `GEMINI_WRITE_SYSTEM_MD=SYSTEM.md`: grava o conteúdo do system prompt em um arquivo `system.md` (o uso de maiúsculas/minúsculas não é preservado). 2. `GEMINI_WRITE_SYSTEM_MD=1`: grava o conteúdo do system prompt em `~/.gemini/system.md` ou no local definido por `GEMINI_SYSTEM_MD`, se configurado. 3. `GEMINI_WRITE_SYSTEM_MD=0` ou `GEMINI_WRITE_SYSTEM_MD=""`: desativa a escrita em disco (Padrão). Nota: existe uma [feature request](https://github.com/google-gemini/gemini-cli/issues/3923) aberta para documentar essas variáveis. Para não precisar escrever um novo system prompt do zero, defini `GEMINI_WRITE_SYSTEM_MD` como `SYSTEM.md` na pasta local do meu projeto e inicializei o Gemini CLI uma vez. Isso dispara a escrita do system prompt no disco. Note que ele não preserva maiúsculas, então, neste exemplo, será gerado como `system.md`, tudo em minúsculas. ```sh $ export GEMINI_WRITE_SYSTEM_MD=SYSTEM.md $ gemini ``` Você verá a tela normal de boot do gemini aparecer: ![Tela de boot do Gemini CLI](https://danicat.dev/pt-br/posts/gemini-cli-system-prompt/image-4.png) Você pode sair do CLI digitando `/quit` ou pressionando Ctrl+D ou Ctrl+C duas vezes. O arquivo `system.md` terá sido gravado no disco. Nota: caso você tenha curiosidade, a gravação ocorre no momento do boot, e não no encerramento. Confira se o arquivo realmente está lá: ```sh $ head -n 10 system.md ``` Aqui está a saída do mesmo comando no meu sistema: ![Saída do comando head em system.md](https://danicat.dev/pt-br/posts/gemini-cli-system-prompt/image-2.png) Agora que temos uma cópia do prompt completo, temos total liberdade para editá-lo como quisermos! Por exemplo, podemos eliminar a problemática linha 49, ou até remover seções inteiras, como aquelas voltadas para desenvolvimento de jogos (a menos que você esteja de fato desenvolvendo jogos — nesse caso, mantenha-as sem dúvida). Quando estiver satisfeito com o novo prompt, defina a variável de ambiente `GEMINI_SYSTEM_MD` apontando para o seu arquivo customizado: ```sh $ export GEMINI_SYSTEM_MD=system.md $ gemini ``` Se você estiver usando um system prompt customizado, notará que no canto inferior esquerdo da tela aparece um ícone de óculos escuros vermelhos: ![Gemini CLI com system prompt customizado ativado](https://danicat.dev/pt-br/posts/gemini-cli-system-prompt/image-3.png) Esse é o sinal de que você não só é a pessoa mais estilosa do planeta, mas também de que está usando um arquivo customizado de system instructions. ## Conclusão Neste artigo, exploramos como customizar sua experiência com o Gemini CLI, tanto pelo caminho habitual com o GEMINI.md quanto pela opção nuclear de sobrescrever o system prompt. Como qualquer técnica avançada, use esse conhecimento com responsabilidade, mas espero que isso ajude você a calibrar o Gemini de acordo com o seu próprio estilo e desfrutar de uma experiência de desenvolvimento muito mais produtiva. Como de costume, adoraria ouvir seu feedback — especialmente sobre quais regras de GEMINI.md ou ajustes de system prompt funcionaram melhor para você. --- **Nota:** Se tiver curiosidade, você pode conferir o arquivo `system.md` que utilizo para este blog no [repositório](https://github.com/danicat/danicat.dev/blob/main/system.md). --- # Um Fluxo de Trabalho Moderno para o Mundo Habilitado por IA - **Markdown URL**: https://danicat.dev/pt-br/posts/developer-workflow-ai-world/index.md - **HTML URL**: https://danicat.dev/pt-br/posts/developer-workflow-ai-world/ - **Date**: 2025-07-11 - **Categories**: Agentic Coding - **Tags**: gemini-cli, jules, vibe-coding - **Summary**: Um framework bidimensional de priorização para fluxos de desenvolvimento com IA. Equilibre certeza técnica e valor de negócio com a Gemini CLI e o Jules.## Introdução Acabei de voltar do WeAreDevelopers World Congress 2025 em Berlim e voltei inspirada pelas inúmeras pessoas desenvolvedoras que conheci de toda a Europa e do mundo. Como esperado, o grande tema deste ano foi IA — IA em todo lugar! Agora temos IA na nossa nuvem, no computador, nos óculos de sol, na torradeira, na pia da cozinha e até no rolo de papel higiênico. Ninguém consegue escapar da IA... nem mesmo os frameworks JS conseguem nascer mais rápido que ela! Estamos CONDENADOS!!! >.< Ou talvez não! Eu sei que estamos vivendo tempos assustadores. A indústria de tecnologia está em transformação. Empresas estão demitindo a torto e a direito sob a premissa de que a IA torna as pessoas mais produtivas ou até substitui pessoas por completo. Se essa é a causa real ou se a IA é apenas um bode expiatório para outros interesses, essa é uma conversa para a mesa de bar e não para este post. Mas vamos ao menos reconhecer que as mudanças estão acontecendo. Uma lição de vida importante que aprendi com o tempo e a experiência é que a gente não deve se preocupar com as coisas que não pode controlar. A IA é inevitável. Portanto, em vez de nos preocuparmos com o que será dos nossos empregos no futuro, convido você para uma reflexão sobre o que a IA pode de fato fazer para melhorar o seu trabalho hoje. É a clássica visão do copo meio cheio: transformar uma crise em oportunidade. Vamos baixar a guarda por um instante e imaginar como pode ser o fluxo de trabalho de quem desenvolve software ao incorporar o "vibe coding" à rotina. Este é, na verdade, um fluxo de trabalho que venho aplicando nas últimas quatro semanas e que tem funcionado muito bem — mas, claro, veja com uma certa reserva, pois ainda não está perfeito. Dito isso, esse ecossistema evolui todos os dias e acredito que só vai melhorar. ## Uma abordagem bidimensional de priorização Antes de entrarmos nas práticas realmente "AI-native" de trabalhar, quero apresentar brevemente um modelo de priorização que utilizo há mais de 7 anos na minha carreira. Aprendi essa abordagem trabalhando na ThoughtWorks em projetos de transformação ágil e a adaptei para as minhas próprias necessidades. O método consiste em reunir as pessoas relevantes na mesma sala para uma discussão — incluindo tanto engenharia quanto stakeholders — e produzir uma sequência lógica para a execução do backlog. Ele se baseia em dois eixos ortogonais: `certeza técnica` (`technical certainty`) e `valor de negócio` (`business value`). ![Resultados do exercício de priorização](image-3.png "Prioridade de implementação com base nos resultados do exercício") A **certeza técnica** mede o quanto o caminho de implementação de uma funcionalidade já está claro. Se a certeza técnica é alta, significa que todos (ou quase todos) os passos necessários para implementar a funcionalidade são conhecidos. Se for baixa, significa que não sabemos como resolver o problema ou temos apenas algumas hipóteses iniciais. O **valor de negócio** indica o quão importante essa funcionalidade é para alcançar os objetivos do time. Alto valor de negócio significa que a feature é crítica para o sucesso do produto; baixo valor indica algo no nível de um "bom ter" (*nice to have*). O grande objetivo deste exercício é destravar aqueles impasses onde **tudo** é considerado prioritário. Mesmo quando tudo **é** importante, colocar os itens lado a lado ajuda até os stakeholders mais resistentes a repensar suas posições em relação às outras prioridades. Além disso, itens de mesmo valor mas com certezas técnicas distintas ganham uma ordem de execução natural: priorizar os "ganhos rápidos" (*low hanging fruit*) primeiro costuma comprar tempo para o time investigar e realizar *spikes*, reduzindo incertezas e elevando a certeza técnica dos itens restantes. ![Modos de trabalho por prioridade](image-2.png "Modo de trabalho recomendado com base na priorização") Como esse processo se relaciona com o trabalho AI-native? Gosto de me ver como a chefe de várias IAs. Eu priorizo meu próprio backlog e defino qual ferramenta alocar para cada tarefa. Se estou fortemente envolvida em uma funcionalidade, dou prioridade para trabalhar com ela de forma síncrona; caso contrário, posso delegá-la para um agente assíncrono. ## Um fluxo de trabalho básico "AI-native" Digamos que eu precise implementar uma nova funcionalidade no meu sistema. Tenho dois modos principais de operação: o **interativo** (síncrono) e o **em lote** (assíncrono, no estilo *fire-and-forget*). A decisão entre um e outro depende diretamente da `certeza técnica` que tenho sobre como implementar aquela funcionalidade e do meu nível de dedicação a ela no momento (`valor de negócio`). ![Exemplo de priorização](image.png "Exemplo de funcionalidades priorizadas para este blog") Por exemplo, neste mesmo blog, escrevi no post anterior ([Como Usei o Jules para Criar um Post em Destaque no Blog](https://danicat.dev/pt-br/posts/20250703-jules-featured-post/)) sobre como implementei o "post em destaque" na página inicial. Quando comecei a trabalhar nisso, eu não sabia nada sobre como implementar um post em destaque, mas tinha uma ideia clara do que queria alcançar. Esse era um problema com baixa certeza técnica, porque eu não conhecia a tecnologia nem os pontos exatos do código que precisaria alterar. Ao mesmo tempo, ele tinha um alto valor de negócio para mim, pois a minha hipótese era de que isso deixaria o blog muito mais profissional e atraente para quem lê. Diante disso, a escolha natural era ter um ciclo de feedback curto: para cada alteração sugerida pela IA, eu precisava avaliar o resultado visual imediatamente e corrigir a rota na direção certa. Por outro lado, para itens de menor prioridade, não há problema em ter um ciclo de feedback longo, o que os torna ideais para o modo em lote. ## Certeza Técnica Baixa/Média OU Alto Valor de Negócio = Modo Interativo (Síncrono) Problemas com menor certeza técnica exigem mais supervisão para fazer as coisas do jeito certo, então prefiro usar um processo interativo com uma ferramenta de CLI. Minha ferramenta de escolha atual é a [Gemini CLI](https://cloud.google.com/gemini/docs/codeassist/gemini-cli?utm_campaign=CDR_0x72884f69_default_b431747616&utm_medium=external&utm_source=blog), lançada pelo Google há apenas duas semanas e que já vem conquistando a comunidade de desenvolvimento em massa. A Gemini CLI é uma aplicação de linha de comando no estilo REPL (*Read-Eval-Print Loop*) potencializada por IA. Você digita um prompt e a CLI reage com uma resposta — que pode ser não apenas código e texto, mas praticamente qualquer coisa graças ao suporte ao Model Context Protocol (MCP). Com isso, você pode usar a CLI para quase tudo, desde pedir um café até atualizar seus bancos de dados. É claro que o caso de uso natural da CLI é codificação, mas vocês sabem como são os desenvolvedores. :) Embora a Gemini CLI conte com um modo YOLO projetado para automação, honestamente ainda não confio nela o suficiente para fazer tarefas sem supervisão (falarei mais sobre isso adiante). Por isso, prefiro usar a CLI quando preciso fazer brainstorming e explorar o espaço do problema antes de chegar a uma solução. Posso pedir para ela planejar uma funcionalidade, pesquisar opções de implementação ou até mesmo implementar direto — apenas para descartar a implementação e recomeçar tudo do zero com o estado limpo, aproveitando os aprendizados da versão inicial (a famosa "prototipagem"). Leva algumas tentativas para eu acertar o prompt, da mesma forma que levaria algumas tentativas para prototipar algo de forma satisfatória codificando manualmente. A diferença brutal é que, em vez de gastar uma semana por protótipo, costumo levar de 30 minutos a uma hora. Em um único dia, consigo validar de 3 a 4 implementações diferentes para o meu problema e, no fim do dia, estou pronta para me comprometer com uma delas, respaldada por dados concretos para tomar uma decisão informada. A principal razão para usar a CLI em problemas de baixa certeza técnica é que o ciclo de feedback se fecha quase imediatamente. Você testa sua hipótese, corrige as arestas e itera. O único tempo de espera é o que o modelo leva para processar a sua requisição. ## Certeza Técnica Alta E Baixo/Médio Valor de Negócio = Modo em Lote (Assíncrono) Um problema com alta certeza técnica, como vimos acima, é aquele em que você já conhece todos (ou quase todos) os passos necessários para implementá-lo. Isso facilita imensamente a sua vida, porque se você já conhece os passos, em vez de executá-los manualmente, pode simplesmente instruir a IA a fazer o trabalho pesado por você. Aqui teríamos um caso para usar a Gemini CLI no modo YOLO, mas na verdade temos uma ferramenta ainda melhor para isso: o [Jules](https://jules.google/). O Jules foi lançado pelo Google no I/O deste ano e rapidamente se tornou a minha ferramenta favorita de todas (com a Gemini CLI logo atrás em segundo lugar). O Jules é um agente assíncrono que você pode conectar ao GitHub para executar tarefas em background. Confesso que, ao descobrir o Jules inicialmente, não prestei atenção suficiente nas letras miúdas e fiquei um pouco frustrada com o quão lento ele parecia. Só depois de um tempo compreendi que a proposta central é justamente disparar a tarefa em segundo plano para que você possa se desconectar e seguir com a sua vida. Como o Jules está integrado ao GitHub, ele já possui todo o contexto do projeto. Você pode pedir tarefas de manutenção como "atualizar a versão das dependências", "implementar testes unitários" ou até "corrigir este bug específico". O ponto fundamental é que, como o ciclo de feedback é longo — você não receberá o resultado na hora —, é melhor reservar essa ferramenta para tarefas nas quais você tem total clareza do que precisa ser feito passo a passo. ## Alta Certeza Técnica E Alto Valor de Negócio = Modo Interativo (Síncrono) Você deve ter notado que, na matriz acima, destaquei que alta certeza técnica combinada com alto valor de negócio é sempre um processo síncrono. O único motivo para isso é que eu me importo demais com esse resultado (alto valor de negócio), então faço questão de supervisioná-lo de perto e garantir que ele fique pronto o mais rápido possível. Para mim, isso é muito natural, pois o valor de negócio sempre se sobrepõe à certeza técnica quando ambos os parâmetros estão no mesmo nível. ## Baixa Certeza Técnica E Baixo Valor de Negócio = Devo realmente fazer isso? Esses são os itens que costumam se perder no limbo do backlog. No mundo pré-IA, eu simplesmente os esqueceria. Mas em um mundo habilitado por IA, se me deparo com eles, costumo disparar uma tarefa no Jules para explorar possibilidades na esperança de reduzir incertezas ou alavancar o valor de negócio. Como o custo cognitivo é muito baixo, você literalmente não tem nada a perder abrindo uma tarefa no Jules, mesmo com o prompt mais básico que imaginar. O resultado pode não ser perfeito, mas como você não faria a tarefa de qualquer maneira, qualquer avanço que sair dali já é lucro. ## Exceções É claro que nenhum bom processo estaria completo sem exceções. Há casos em que delego tarefas de alto valor de negócio para o Jules, e isso geralmente acontece quando eu não teria como realizá-las de outra forma. Por exemplo: 1. Estou em um evento e não consigo abrir o laptop para implementar uma ideia, mas estou com o celular em mãos 2. Estou viajando com uma conexão instável ou lenta 3. Estou presa na fila do supermercado, lembro de algo que deixei pendente e quero dar o pontapé inicial para quando chegar em casa Em resumo: se as alternativas forem fazer zero progresso ou acionar o Jules, prefiro colocar o Jules para trabalhar por mim. Por outro lado, ainda não comentei onde as IDEs tradicionais se encaixam no meu fluxo de trabalho. Isso não significa que as abandonei — na verdade, estou escrevendo este post no VS Code agora mesmo. Reservo a IDE e a edição manual para a última milha ou o acabamento final do código. Apenas tome MUITO cuidado com pequenas edições manuais no meio de uma sessão de "vibe coding", pois elas tendem a desorientar o contexto dos LLMs com facilidade. Mas se essa for a etapa final do processo, você estará em total segurança. ## Bônus: Uma nota sobre redução de incerteza Às vezes você só precisa explorar um assunto, mas ainda nem sabe como integrá-lo à sua base de código. Tentar forçar a Gemini CLI ou o Jules a realizar tarefas que não envolvem código é como usar uma marreta para fixar um parafuso na parede. Nesses cenários de pesquisa pura, prefiro recorrer ao [Gemini Deep Research](https://gemini.google/overview/deep-research/?hl=en-GB). Assim como o Jules, o Gemini Deep Research é assíncrono: você dispara a pesquisa em segundo plano e segue o seu dia. Se você já estiver diante do teclado e não quiser esperar, usar o Gemini padrão com grounding na Busca do Google também faz maravilhas. Ambas as ferramentas tendem a produzir relatórios detalhados e um tanto extensos. Se você tiver pouca paciência para leituras longas (como eu), um ótimo truque é enviar os resultados da pesquisa para o [NotebookLM](https://notebooklm.google/) e pedir para ele gerar um resumo ou produzir um podcast em áudio para você ouvir no caminho. ## Conclusões Todo o processo de seleção de ferramentas de IA e o modelo de trabalho baseado no exercício de priorização podem ser resumidos da seguinte forma: ![Resumo das ferramentas recomendadas por prioridade](image-1.png "Resumo das ferramentas e modos de trabalho recomendados") 1. **Alta Certeza Técnica + Alto Valor de Negócio** = processo síncrono ou pair programming com a Gemini CLI. Use o Gemini com grounding na busca para esclarecimentos pontuais. 2. **Baixa/Média Certeza Técnica + Alto Valor de Negócio** = processo síncrono com a Gemini CLI somado a pesquisa assíncrona para aumentar a certeza técnica. 3. **Alta Certeza Técnica + Baixo/Médio Valor de Negócio** = processo assíncrono com o Jules. Deep Research se necessário. 4. **Baixa Certeza Técnica + Baixo Valor de Negócio** = na maioria dos casos, não faça; se você realmente quiser avançar, use o Jules ou Deep Research para elevar um dos parâmetros. O que você acha dessa metodologia de trabalho? Por favor, compartilhe seus comentários e impressões abaixo! --- # Como Usei o Jules para Adicionar um Post em Destaque Neste Blog - **Markdown URL**: https://danicat.dev/pt-br/posts/jules-featured-post/index.md - **HTML URL**: https://danicat.dev/pt-br/posts/jules-featured-post/ - **Date**: 2025-07-03 - **Categories**: Agentic Coding - **Tags**: jules, tutorial, vibe-coding - **Summary**: Estudo de caso sobre o uso do agente Jules para implementar uma seção de post em destaque no Hugo com Tailwind CSS. Aborda iteração de prompts, estilização e aprendizados. > *Nota da autora:* cerca de 90% deste post foi escrito por IA, mas revisei e editei o texto para garantir uma leitura agradável e fluida. Foi engraçado notar a tendência do Jules de se autoelogiar. Precisei guiá-lo com vários prompts até chegar a este resultado final, embora a última edição tenha sido mais fácil de fazer manualmente. Você pode conferir todo o histórico de edições no [histórico de commits do PR](https://github.com/danicat/danicat.dev/pull/6). Vale mencionar que ele se recusou terminantemente a traduzir este post para o português (Brasil), alegando que não tinha capacidades de tradução — mesmo com a totalidade deste blog tendo sido traduzida usando o Jules em uma interação anterior. Acho que ele só não estava no clima mesmo. :) ## Introdução Recentemente, decidi atualizar a página inicial do meu blog para destacar melhor o conteúdo mais recente. Como engenheira de backend, mergulhar nas minúcias do frontend não faz parte do meu dia a dia. Por isso, em vez de codificar manualmente todas as mudanças em um domínio com o qual tenho menos familiaridade, recorri à ajuda do [Jules](https://jules.google), um assistente de programação com IA. Este post detalha a nossa jornada iterativa: os sucessos, os mal-entendidos (alguns bem divertidos) e o que aprendi sobre como trabalhar de forma eficaz com IA no desenvolvimento web, especialmente para preencher lacunas de habilidades. ## O Objetivo: Uma Seção de Post em Destaque Minha solicitação inicial para o Jules foi bem direta: > "Change the layout of the main page so that it displays the most recent blog post in highlight instead of it being in the recent posts list. The recent posts should contain all other posts except the most recent one. This behaviour should be seen only on the blog landing page (home). If the user clicks on the Blog menu it should still see all the posts in reverse chronological order, including the most recent one." O Jules compreendeu rapidamente e propôs um plano envolvendo a exploração da base de código do Hugo, a identificação dos templates e sua modificação. ## Destaques das Iterações: O Bom, o Ruim e a IA Nossa colaboração envolveu várias iterações para acertar todos os detalhes. ### Iteração 1: Configuração Inicial — Acertando a Base O Jules identificou corretamente os partials do tema Blowfish e estruturou os overrides. A lógica para separar o post mais recente dos demais na lista de "Posts Recentes" foi muito bem implementada. * **O que funcionou:** Compreensão da estrutura central do Hugo, busca de posts e modificações básicas nos templates. A capacidade do Jules de navegar pelos arquivos do tema e do projeto poupou um tempo significativo aqui. ![Tela de aguardo de execução de tarefas no agente Jules](images/image001.png "Houve bastante tempo de espera entre as tarefas") ### Iteração 2: Estilização com Tailwind — A Dança da Tentativa e Erro Passamos a focar na aparência: título, largura e dimensões da imagem. Isso envolveu uma série de prompts para afinar os visuais. Por exemplo: > "Change the featured post title to 'Featured Post'. Adjust its width to be about 80% of the view. The image is too tall/narrow, let's try a 4:3 aspect ratio. That's still not quite right, make it wider/less tall." Foi aqui que a natureza iterativa de trabalhar com o Jules em elementos visuais ficou muito evidente. * **A abordagem do Jules:** Modificou arquivos de i18n para os títulos, utilizou diversas classes de largura do Tailwind (por exemplo, `md:w-4/5`, `md:w-2/3`, `max-w-xl`, `max-w-2xl`) e manipulou `padding-bottom` para controlar o aspect ratio das imagens. * **Desafio e Frustração:** Um obstáculo específico, especialmente para alguém como eu que trabalha primariamente no backend, foi a natureza de tentativa e erro ao estilizar com Tailwind por meio de um intermediário. Embora o Jules pudesse aplicar as classes que julgava apropriadas, o resultado visual nem sempre correspondia de imediato ao que eu tinha em mente. Mudanças nas classes do Tailwind frequentemente não se traduziam em uma diferença claramente visível na primeira tentativa, ou o efeito não saía como esperado. Isso levou a algumas rodadas de "tente esta classe", "não, faça mais estreito/mais largo/mais alto/mais baixo", o que, embora bem-sucedido no fim das contas, foi um tanto frustrante em certos momentos. Ficou evidente o descompasso entre mexer no código e obter feedback visual imediato nesse fluxo de trabalho assíncrono mediado por IA. * **Aprendizado:** Fazer o ajuste fino da estética visual é a parte de que menos gosto nessa experiência, pois as instruções frequentemente geram interpretações imprecisas. Fornecer um feedback claro e descritivo é fundamental, mas também é preciso reconhecer que certo vai-e-vem é inevitável quando não se pode apontar diretamente para a tela ou fazer microajustes em tempo real por conta própria. Ainda assim, o Jules aplicou com dedicação cada alteração solicitada, ajudando a suprir minha menor familiaridade com o frontend. ### Iteração 3: CSS Customizado vs. Tailwind — Um Breve Desvio A certa altura, para ter um controle muito específico sobre as dimensões do card, passei o seguinte prompt: > "jules, instead of trying to use an existing style class, create an unique style class for the featured post card. This style should use relative width and height of 75% of the container..." * **Resposta do Jules:** O Jules criou corretamente as regras de CSS customizado e refatorou o partial do card para utilizá-las. * **Resultado e Aprendizado:** Embora o Jules tenha implementado exatamente o solicitado, o resultado pareceu um tanto estranho em relação ao restante do design do blog, que é fortemente baseado em Tailwind. O CSS customizado não harmonizou muito bem, e rapidamente decidi que manter a consistência com o Tailwind era mais importante. Foi uma ótima lição sobre garantir que soluções geradas por IA respeitem a linguagem de design existente e a preferência por aderir ao framework já estabelecido. O Jules retornou prontamente ao Tailwind quando solicitei: > "undo the last change and restore the tailwind style of formatting. apply the same style guidelines using tailwind best practices" ### Iteração 4: O Grande Mal-Entendido dos "Comentários"! Esta foi talvez a parte mais ilustrativa da interação entre humano e IA. Eu mencionei: > "the comments are rendering in the featured post. please remove all the comments or make them invisible" * **A interpretação do Jules:** O Jules presumiu que eu me referia ao *sistema de comentários dos usuários* do blog (como Utterances ou Giscus) ou a metadados como contadores de visualizações/curtidas. Isso gerou uma sequência de etapas em que o Jules tentou investigar e ocultar condicionalmente os metadados de visualizações/curtidas. * **Meu esclarecimento:** Após essas mudanças, esclareci fornecendo um exemplo concreto: > "you are wrong, I never said I wanted to remove the views and likes - I'm referring to the code comments in rendering as {/* Adjusted padding ... */} and {/* Removed prose classes ... */}" * **Resolução:** Assim que o Jules compreendeu que eu me referia a *comentários literais de código em Go template/HTML* incorretamente formatados (usando `{/*...*/}`, que não é uma sintaxe válida de comentário no Hugo e por isso renderizava como texto comum) e não `{{/* ... */}}`, a correção foi imediata: remover o texto indevido dos templates. * **O que funcionou:** A persistência e a abordagem sistemática do Jules para depurar o problema (mesmo tendo partido de uma premissa incorreta) foram louváveis. * **Desafio e Aprendizado:** O caso evidenciou um aspecto crucial da interação com IA: a ambiguidade na linguagem natural. A palavra "comentários" tem múltiplos significados. Meu relato inicial não havia sido preciso o suficiente. ### Iteração 5: Polimento Final Após resolvermos os comentários visíveis do template, fizemos os retoques finais: > "Remove the 'Featured Post' title. Change card width to 50%. Increase title and summary font sizes. Make the image's aspect ratio 16:9." Isso levou aos ajustes finais na largura do card, tamanhos de fonte e proporção da imagem. A porcentagem de largura em si não surtiu efeito, mas alterar o aspect ratio resolveu com perfeição. ### Iteração Bônus: O Jules Rascunha Este Post > "This is perfect. No more code changes are needed. Now I want you to create a new blog post entry describing the iteration we just did..." E cá estamos! Este próprio post foi redigido com a assistência do Jules, a partir do nosso histórico de interações e das minhas orientações de refinamento — incluindo as exatas reflexões que você acabou de ler. ## O Que Funcionou Bem com o Jules * **Preenchendo Lacunas de Habilidades:** Como engenheira de backend, o suporte do Jules foi valioso para encarar tarefas de frontend envolvendo templates do Hugo e Tailwind CSS, áreas onde tenho menos experiência no dia a dia. O Jules compensou minha falta de conhecimento aprofundado em frontend, propondo e implementando soluções que coube a mim apenas direcionar e refinar. * **Velocidade de Implementação:** Para alterações bem delineadas, o Jules modifica código, cria arquivos e refatora estruturas com muito mais agilidade do que a digitação manual. * **Processamento de Instruções Complexas:** De modo geral, o Jules compreendeu solicitações em múltiplas etapas e metas complexas de layout. * **Resolução Sistemática de Problemas:** Mesmo diante de mal-entendidos, o Jules frequentemente seguiu uma linha de raciocínio lógica e estruturada. * **Refinamento Iterativo:** O Jules se manteve consistentemente receptivo aos feedbacks para ajustes. ## Desafios e Aprendizados * **Precisão da Linguagem:** O episódio dos "comentários" ressalta o quão crítica é a precisão na comunicação. O que parece óbvio para um desenvolvedor, ou uma simplificação comum, pode soar ambíguo para uma IA. * **Loop de Feedback Visual e Tailwind:** O processo de tentativa e erro ao estilizar com Tailwind foi um desafio central. Como o Jules não "enxerga" o resultado final, descrever o visual desejado ou explicar por que determinado grupo de classes não estava funcionando exigiu paciência e descrições detalhadas. Essa é uma característica intrínseca da interação puramente textual para tarefas visuais. * **Interpretação Equivocada e Correção de Rota:** Quando o Jules interpretava mal uma tarefa, ele avançava diligentemente por aquele caminho incorreto. Não havia como interrompê-lo no meio do processo; era necessário aguardar a conclusão da sequência de ações em andamento para fornecer o feedback corretivo. * **Fluxo de Trabalho Assíncrono e Ritmo:** O trabalho é predominantemente assíncrono. Cada solicitação e execução do Jules podia levar de alguns minutos até meia hora em sequências mais complexas. Isso torna o ciclo de iteração mais compassado do que programar diretamente com feedback instantâneo ou fazer pair programming ao vivo. ## Recursos Sugeridos Para quem tiver interesse em conhecer mais sobre o Jules: * [Site Oficial do Jules](https://jules.google) * [Documentação do Jules](https://jules.google/docs) ## Conclusão No geral, trabalhar com o Jules para construir o recurso da homepage foi uma experiência produtiva. Teve um gostinho autêntico de "vibe-coding" — uma troca dinâmica conduzindo a IA. O segredo para o sucesso está em manter uma comunicação clara e iterativa, demonstrar paciência diante de mal-entendidos e fornecer feedbacks pontuais e acionáveis. As frustrações, sobretudo com as tentativas e erros no Tailwind e as eventuais interpretações equivocadas da IA, fazem parte do cenário atual do desenvolvimento assistido por inteligência artificial. Contudo, apesar da natureza assíncrona e do tempo de espera em certas gerações, delegar a parte mecânica da programação e obter sugestões para áreas fora da minha especialidade (como implementações específicas no Tailwind ou na estrutura do Hugo) ainda gerou um saldo extremamente positivo. Foi consideravelmente mais rápido e prático do que se eu tivesse tentado aprender do zero todos os princípios de design de frontend, as peculiaridades do Hugo e os detalhes do Tailwind CSS apenas para entregar essa funcionalidade. Assistentes de IA como o Jules são ferramentas poderosas. Eles não substituem o julgamento crítico nem a intenção arquitetural do engenheiro, mas se tornam aceleradores extraordinários quando combinados com a mentalidade e a estratégia de comunicação corretas. --- # Do Prompt à Infraestrutura com o Gemini Cloud Assist - **Markdown URL**: https://danicat.dev/pt-br/posts/gemini-cloud-assist/index.md - **HTML URL**: https://danicat.dev/pt-br/posts/gemini-cloud-assist/ - **Date**: 2025-07-02 - **Categories**: Applied GenAI - **Tags**: gemini, google-cloud, tutorial - **Summary**: Projete infraestrutura no Google Cloud usando linguagem natural com o Gemini Cloud Assist e o Application Design Center. Gere e exporte módulos Terraform prontos.## Introdução Hoje vamos fazer um pequeno desvio do nosso conteúdo habitual sobre agentes de IA para falar sobre um produto que explorei recentemente durante a minha participação no I/O Connect Berlin 2025, na semana passada. O evento reuniu mais de 1.000 pessoas desenvolvedoras de toda a Europa, incluindo membros das comunidades do Google (Google Developer Groups) e experts da comunidade. Foi também o meu primeiro evento oficial do Google desde que entrei para a equipe de DevRel em abril, o que tornou tudo ainda mais especial para mim — e é por isso que não tivemos post no blog na semana passada! Fui responsável por uma demonstração chamada “Design and Deploy”, que apresentou a combinação de dois produtos: o [Application Design Center (ADC)](https://cloud.google.com/application-design-center/docs/overview?utm_campaign=CDR_0x72884f69_awareness_b428663487&utm_medium=external&utm_source=blog) e o [Gemini Cloud Assist (GCA)](https://cloud.google.com/products/gemini/cloud-assist?utm_campaign=CDR_0x72884f69_awareness_b428663487&utm_medium=external&utm_source=blog). A demo foi tão bem recebida que decidi trazer o conteúdo para o blog também, dando a oportunidade de experimentar essa tecnologia a quem não pôde estar lá presencialmente. O Application Design Center é um produto criado para ajudar arquitetos e desenvolvedores a desenhar a infraestrutura de suas aplicações. Na camada visual, ele oferece uma interface intuitiva na qual você define os componentes da infraestrutura graficamente; por baixo dos panos, porém, tudo o que aparece na tela é representado como um módulo Terraform, permitindo aproveitar todos os benefícios de [Infraestrutura como Código (IaC)](https://en.wikipedia.org/wiki/Infrastructure_as_code). Um aviso importante é que o ADC está atualmente em [public preview](https://cloud.google.com/products?e=48754805&hl=en#product-launch-stages&utm_campaign=CDR_0x72884f69_awareness_b428663487&utm_medium=external&utm_source=blog). Isso significa que o produto evolui diariamente e pode, eventualmente, quebrar a compatibilidade com versões anteriores. Ele também conta com algumas arestas que precisam ser lapidadas antes da disponibilidade geral (GA), as quais vou destacar adiante. Já o Gemini Cloud Assist (também em public preview) é o nome oficial do suporte do Gemini no Google Cloud. Por isso, o GCA não é um produto isolado, mas sim uma camada conectiva que permite interagir com qualquer recurso do GCP em linguagem natural, trazendo todas as vantagens da experiência moderna de chat baseada em modelos de linguagem (LLMs). Vamos ver como combinar essas duas tecnologias para desenhar rapidamente a infraestrutura de uma aplicação. ## Como iniciar uma sessão de design de aplicação Você sempre pode abrir o Application Design Center manualmente pelo console do Google Cloud, mas onde estaria a graça nisso? O jeito mais prático de acionar o ADC para um novo desenho é abrir o painel do Gemini a partir de qualquer página. Aqui, por exemplo, estou na página inicial (Welcome) do meu projeto: ![Tela inicial no console do Google Cloud](images/image001.png "Tela inicial no console do Google Cloud") Ao clicar no botão de “estrela” no canto direito da barra de pesquisa, você abre o painel do Gemini Cloud Assist: ![Visão ampliada do botão do Gemini](images/image002.png "Visão ampliada do botão do Gemini") A interface exibida será esta: ![Tela inicial do Google Cloud Assist](images/image003.png "Tela inicial do Google Cloud Assist") Nesse painel você pode interagir diretamente com o Gemini. Digite algo como “crie uma aplicação que faz X” e inclua quantos detalhes arquiteturais desejar. Por exemplo, vamos criar uma aplicação que gera imagens de gatos. Aqui está o prompt: > Crie uma aplicação que gere imagens de gatos com o Gemini e as armazene em um banco de dados Cloud SQL. Os usuários podem solicitar novas imagens usando um serviço de geração e visualizar as imagens criadas por meio de um serviço de imagens. Ambos os serviços devem ser expostos através de um serviço de frontend e um balanceador de carga global. Após enviar o prompt, o Gemini processará a requisição por alguns instantes e apresentará um resultado como este: ![Resposta do Gemini com diagrama de arquitetura](images/image004.png "Resposta do Gemini com diagrama de arquitetura") A visualização integrada dá uma boa ideia inicial, mas podemos interagir muito melhor com o desenho clicando no botão “Edit app design”. Isso abrirá a arquitetura em modo expandido para refinamento. (Nota: o restante deste artigo assume que o botão “Edit app design” abre a janela de Preview. Caso isso não aconteça no seu ambiente, consulte as observações ao final do texto). É assim que o desenho aparece na janela de “Preview”: ![Janela de Preview do Gemini Cloud Assist](images/image005.png "Janela de Preview do Gemini Cloud Assist") Caso queira ajustar as convenções de nomenclatura ou os detalhes dos componentes gerados, basta clicar sobre qualquer componente para abrir o painel de configuração. Aqui abri os detalhes do meu `frontend-service`: ![Visão do painel de detalhes do componente](images/image006.png "Visão do painel de detalhes do componente") Repare que a tela também exibe a imagem de contêiner instanciada pelo Cloud Run, que por padrão vem como um contêiner “hello”. Isso acontece porque o Gemini Cloud Assist não sabe qual imagem você pretende rodar, mas você pode informar esse valor para que ele faça a substituição. Vale reforçar esse ponto para alinhar expectativas: a ferramenta não escreve o código da aplicação por você, ela apenas projeta a infraestrutura necessária para executá-la. Para programar os serviços de frontend e backend, por exemplo, você continuará usando outras ferramentas como a Gemini CLI ou sua IDE de preferência, publicando os artefatos no seu container registry para que o Cloud Run possa acessá-los. Na janela de Preview é possível editar componentes existentes, mas não adicionar novos blocos manualmente. Se quiser iterar no design, basta pedir as alterações ao Gemini em linguagem natural. Veja este prompt de continuidade: > Adicione um serviço de streaming que capture eventos para cada imagem de gato gerada. Do outro lado do stream, inclua um serviço consumidor que atualizará uma página estática hospedada no GCS, adicionando as fotos mais recentes a um feed. E esta foi a resposta do Gemini: ![Resposta do Gemini ao prompt complementar](images/image007.png "Resposta do Gemini ao prompt complementar") A janela de Preview é atualizada com o novo design, destacando adições (em verde), modificações (em azul) e remoções (em vermelho): ![Alterações propostas no diagrama](images/image008.png "Alterações propostas no diagrama") Na parte inferior da tela, você tem a opção de aceitar ou rejeitar a sugestão. Mas antes disso, é uma boa oportunidade para inspecionar o código Terraform gerado nos bastidores. Para visualizar o código e comparar as mudanças, clique em “View diff”: Isso abrirá a janela de Code Diff com ambas as versões lado a lado: ![Janela de diff comparando o código Terraform antes e depois](images/image009.png "Janela de diff comparando o código Terraform antes e depois") Como você pode ver, cada caixa no diagrama é mapeada para um módulo Terraform específico. Rolando até o fim da página, os módulos adicionados recentemente aparecem destacados em verde. Se a implementação estiver satisfatória, você pode aceitar a proposta ou rejeitá-la e pedir melhorias ao Gemini. Eu aceitei a sugestão, mas notei algo curioso no módulo “database-secrets” e resolvi questionar o Gemini: Prompt: “por que você adicionou um database secret se o banco Cloud SQL está usando autenticação IAM?” Pois é, no fim das contas não era realmente necessário: ![Resposta do Gemini à pergunta sobre autenticação IAM](images/image010.png "Resposta do Gemini à pergunta sobre autenticação IAM") Na janela de Preview: ![Proposta do Gemini para remover o segredo do banco de dados](images/image011.png "Proposta do Gemini para remover o segredo do banco de dados") Esse é um lembrete importante: por mais avançada que a IA seja, ela ainda não nos isenta de avaliar e tomar decisões. No final das contas, a IA ainda vai continuar lá, mas é o nosso emprego que está em jogo — então não se esqueça de validar tudo. 🙂 Falando em validações, outro detalhe que me chamou a atenção foi o tipo de instância sugerido pelo Gemini para o Cloud SQL: `db-perf-optimized-N-8`. Vamos tentar outro prompt para otimizar os custos, já que essa máquina é nitidamente exagerada para um protótipo inicial: > Torne a arquitetura mais econômica (Make it cost effective) ![Resposta do Gemini sugerindo um balanceador regional e trocando Postgres por MySQL](images/image012.png "Resposta do Gemini sugerindo um balanceador regional e trocando Postgres por MySQL") Hummm… essa resposta me fez pensar. Entendo o ponto sobre balanceador regional vs. global, mas não me convenci de por que ele considera o MySQL mais econômico que o PostgreSQL. Minha preocupação principal era com o tipo de máquina, não com a tecnologia do banco em si. A explicação do Gemini também não conta a história toda. Ao inspecionar o diff com atenção, vi que ele de fato alterou o tipo de máquina (pelo atributo `tier`), só esqueceu de mencionar na mensagem: ![Diff do Terraform mostrando que o Gemini também alterou o tipo de máquina (tier)](images/image013.png "Diff do Terraform mostrando que o Gemini também alterou o tipo de máquina (tier)") Não fiquei totalmente satisfeita com isso, então perguntei o porquê: > Por que você considera o MySQL mais econômico que o PostgreSQL? ![Perguntando ao Gemini por que ele considera o MySQL mais econômico que o Postgres](images/image014.png "Perguntando ao Gemini por que ele considera o MySQL mais econômico que o Postgres") A resposta alegou que o MySQL seria mais econômico que o Postgres devido a: 1. Diferenças de licenciamento 2. Consumo de recursos 3. Preço do serviço gerenciado Infelizmente não posso concordar com essa resposta. Quanto ao item 1, ambos têm licenças de código aberto, então não são tão diferentes assim. O item 2 pode até ter algum fundo de verdade, mas eu ainda precisaria de um benchmark adequado. Já o item 3 está errado, porque o Cloud SQL para Postgres e para MySQL possui exatamente o mesmo modelo de preços no GCP. Mais um ponto para os humanos! Vamos reverter a alteração: > Reverta a alteração de postgres para mysql, mas mantenha o tipo de máquina menor. Inspeção final: fiquei satisfeita com o Cloud SQL rodando Postgres em um tier menor de banco de dados, mas também notei outra alteração relevante que habilita o recurso de scale-to-zero no Cloud Run: ![Diff do Terraform mostrando que o Cloud Run foi configurado com scale-to-zero (min_instance_count = 0)](images/image015.png "Diff do Terraform mostrando que o Cloud Run foi configurado com scale-to-zero (min_instance_count = 0)") Essa alteração faz todo o sentido, mas também não foi mencionada no diálogo. Esse é outro lembrete de “confie, mas verifique” tudo o que as ferramentas de IA sugerem. Não queremos surpresas rodando em produção! ## Baixando os arquivos Terraform Quando estiver tudo pronto com o seu desenho, basta clicar no botão “<> Get Code” no canto superior direito da tela. A ferramenta vai empacotar todo o código Terraform subjacente em um arquivo `.zip` para download na sua máquina local. Infelizmente, no momento em que escrevo este artigo, o Application Design Center não oferece suporte a integrações com sistemas de versionamento de código como GitHub, GitLab, Google Source, Bitbucket e outros. A única forma de extrair o código da ferramenta é por meio desse download em arquivo zip. Para quem usa contas corporativas com uma hierarquia de organização completa, é possível pegar esse design e fazer o deploy usando o AppHub. Mas se você estiver usando uma conta pessoal, infelizmente este é o limite do que a ferramenta consegue fazer por você. ## Observações sobre a interface do App Design Center O botão “Edit app design” tem comportamentos diferentes dependendo de como o seu console do Google Cloud está configurado. Se você estiver testando esse prompt a partir de uma conta pessoal e essa conta não estiver associada a uma organização, ele abrirá uma janela de Preview onde é possível visualizar o design e baixar o código Terraform correspondente, mas você não terá acesso à interface completa do App Design Center. Para usar a interface completa, você precisa fazer parte de uma organização, pois a configuração do App Design Center exige um tipo especial de pasta configurada, conhecida como pasta “app design center enabled”. Não há como criar pastas em contas sem organização e, dentro de uma organização, essa pasta precisa ser configurada pelo administrador de nuvem. Infelizmente, isso significa que contas de usuário que não pertencem a nenhuma organização ficarão efetivamente impedidas de acessar o conjunto completo de recursos do ADC, pelo menos por enquanto. Você ainda poderá usar o Gemini para ajudar a prototipar a arquitetura da sua aplicação como mostrei neste artigo, mas não poderá salvar seu progresso na interface web da nuvem e precisará baixar os arquivos Terraform para a sua máquina local e fazer o deploy usando a sua própria instalação do Terraform. ## Conclusões e próximos passos Cada novo produto de IA lançado me deixa empolgada com a ideia de viver aquele momento "Tony Stark", em que você projeta seu software apenas com comandos de voz. Ainda não chegamos lá, mas com o Gemini Cloud Assist estamos fazendo um bom progresso, já que agora podemos usar linguagem natural para especificar os componentes de infraestrutura por nós. Ainda existem algumas arestas a serem aparadas, tanto na interface quanto nas sugestões do Gemini, mas já fico aliviada de não precisar escrever código Terraform manualmente do zero para cada nova aplicação que começo a desenvolver. Este é claramente um artigo que deve ter data de validade, pois devemos ver essas ferramentas evoluírem muito rápido nos próximos meses. Para se manter atualizado, você sempre pode conferir a página do produto [Application Design Center](https://cloud.google.com/application-design-center/docs/overview?utm_campaign=CDR_0x72884f69_awareness_b428663487&utm_medium=external&utm_source=blog), e com certeza farei o meu melhor para trazer as novas funcionalidades e melhorias interessantes aqui no blog também. Como sugestão, recomendo experimentar alguns prompts criativos como “make it cost effective” (torne mais econômico), “make it highly available” (garanta alta disponibilidade), “explain why x instead of y” (explique por que x em vez de y), “replace x with y” (substitua x por y), “explain x to me like I’m 5” (me explique x como se eu tivesse 5 anos), e assim por diante. O que você achou? Achou essa ferramenta empolgante ou assustadora? Descobriu algum prompt interessante? Deixe seus comentários abaixo! --- # Prompt Audacioso: Um Guia Prático para Instruções de Sistema e Ferramentas - **Markdown URL**: https://danicat.dev/pt-br/posts/system-instructions-agent-tools/index.md - **HTML URL**: https://danicat.dev/pt-br/posts/system-instructions-agent-tools/ - **Date**: 2025-06-11 - **Categories**: Agent Development - **Tags**: gemini, python, tutorial, vertex-ai - **Series**: Building the Diagnostic Agent - **Summary**: Aprenda a combinar instruções de sistema, histórico de chat e ferramentas osquery em Python com a Vertex AI para construir um assistente de diagnóstico autônomo.## Introdução Neste guia, vamos nos aprofundar em prompts de sistema (system instructions) e ferramentas para agentes (agent tools), evoluindo a experiência do nosso assistente de diagnóstico. Trabalharemos com o [SDK da Vertex AI para Python](https://cloud.google.com/vertex-ai/docs/python-sdk/use-vertex-ai-python-sdk?utm_campaign=CDR_0x72884f69_awareness_b424142426&utm_medium=external&utm_source=blog), LangChain, Gemini e [Osquery](https://www.osquery.io/). Preciso admitir: a [versão inicial do agente de diagnóstico](https://danicat.dev/pt-br/posts/20250531-diagnostic-agent/) não estava tão pronta para a "Enterprise" (com o perdão do trocadilho). Não tínhamos visibilidade do que ele fazia por baixo dos panos (estava realmente rodando queries SQL?), ele não se lembrava do que havia sido discutido na mesma sessão e, de vez em quando, ignorava nossos comandos por completo. Isso está longe da experiência ideal para um agente autônomo. Um agente de diagnóstico confiável precisa lembrar de seus erros e executar instruções com consistência, por exemplo, descobrindo que certas colunas não existem e contornando o problema. Além disso, precisamos auditar o que ele faz em tempo de execução para garantir que as respostas sejam precisas e atualizadas. Com esses objetivos em mente, vamos colocar a mão na massa e construir o nosso ~~Holograma Médico~~ Agente de Diagnóstico de Emergência! ## Preparando o ambiente Na Parte 1, usamos um Jupyter Notebook pela conveniência, mas agora vamos estruturar um programa Python convencional. O mesmo código funcionaria no Jupyter com poucas alterações, mas optamos por um script padrão para criar uma interface de chat interativa no terminal. Recomendo criar um ambiente virtual limpo para isolar as dependências: ```shell mkdir -p ~/projects/diagnostic-agent cd ~/projects/diagnostic-agent python3 -m venv venv source venv/bin/activate pip install --upgrade google-cloud-aiplatform[agent_engines,langchain] rich ``` Aqui está a versão inicial de `main.py` que reproduz o agente do artigo anterior: ```python import os import vertexai from vertexai import agent_engines import osquery from rich.console import Console from rich.markdown import Markdown PROJECT_ID = os.environ.get("GCP_PROJECT") LOCATION = os.environ.get("GCP_REGION", "us-central1") STAGING_BUCKET = os.environ.get("STAGING_BUCKET_URI") vertexai.init( project=PROJECT_ID, location=LOCATION, staging_bucket=STAGING_BUCKET ) MODEL = os.environ.get("GEMINI_MODEL", "gemini-2.0-flash") instance = osquery.SpawnInstance() def call_osquery(query: str): """Consulta o sistema operacional usando o osquery. Esta função envia uma query ao processo do osquery para retornar dados sobre a máquina atual, sistema operacional e processos em execução. Você também pode consultar o banco SQLite subjacente para inspecionar metadados usando tabelas de sistema como sqlite_master, sqlite_temp_master e tabelas virtuais. Args: query: str Uma query SQL para uma das tabelas do osquery (ex: "select timestamp from time") Returns: ExtensionResponse: resposta do osquery com o status da requisição e o resultado da query. """ if not instance.is_running(): instance.open() result = instance.client.query(query) return result def get_system_prompt(): if not instance.is_running(): instance.open() response = instance.client.query("select name from sqlite_temp_master").response tables = [ t["name"] for t in response ] return f""" Papel: - Você é o agente de diagnóstico de emergência. - Você é o último recurso do usuário para diagnosticar problemas no computador. - Responda às consultas do usuário com o melhor de suas capacidades. Ferramentas: - Você pode consultar o osquery usando a função call_osquery. Contexto: - Utilize apenas tabelas desta lista: {tables} - Você pode descobrir schemas usando: PRAGMA table_info(tabela) Tarefa: - Crie um plano de quais tabelas consultar para atender ao pedido do usuário. - Confirme o plano com o usuário antes de executar. - Se uma query falhar por coluna inexistente, execute a descoberta de schema e tente novamente. - Consulte a(s) tabela(s) necessária(s). - Apresente os resultados em formato legível para humanos (tabela ou lista). """ def main(): agent = agent_engines.LangchainAgent( model = MODEL, system_instruction=get_system_prompt(), tools=[ call_osquery, ], ) console = Console() print("Bem-vindo ao Agente de Diagnóstico de Emergência\n") print("Qual é a natureza da sua emergência de diagnóstico?") while True: try: query = input(">> ") except EOFError: query = "exit" if query in ("exit", "quit"): break if query.strip() == "": continue response = agent.query(input=query) rendered_markdown = Markdown(response["output"]) console.print(rendered_markdown) print("Até logo!") if __name__ == "__main__": main() ``` Você pode executar o agente com `python main.py`: ``` $ python main.py Bem-vindo ao Agente de Diagnóstico de Emergência Qual é a natureza da sua emergência de diagnóstico? >> ``` Há duas mudanças em relação à versão original: primeiro, adicionamos um loop interativo no terminal que mantém o agente em execução até que o usuário digite `exit` ou `quit`. Segundo, ajustamos o prompt de sistema. Agora o chamamos de "Agente de Diagnóstico de Emergência", um nome que, além de ser um easter egg de Star Trek, estabelece um tom de urgência que incentiva o modelo a seguir as instruções com maior rigor e menos recusas indevidas. ## Estruturando Instruções de Sistema (System Prompts) Instruções de sistema definem as regras e restrições que orientam o modelo durante toda a conversa. Elas possuem prioridade sobre as mensagens comuns do usuário. Embora não exista uma única fórmula mágica para estruturar prompts de sistema, dividir o prompt em seções bem delimitadas facilita a compreensão do modelo: **Papel (Role)**, **Ferramentas (Tools)**, **Contexto (Context)** e **Tarefa (Task)**. ### 1. Papel (Role) Define a identidade e a finalidade do agente. Para um LLM treinado em vastos domínios de conhecimento, o papel restringe o contexto semântico. Por exemplo, a pergunta "o que são cookies?" tem significados completamente diferentes na culinária e no desenvolvimento web. ``` Papel: - Você é o agente de diagnóstico de emergência. - Você é o último recurso do usuário para diagnosticar problemas no computador. - Responda às consultas do usuário com o melhor de suas capacidades. ``` ### 2. Ferramentas (Tools) Informa ao agente quais capacidades externas estão à sua disposição: ``` Ferramentas: - Você pode consultar o osquery usando a função call_osquery. ``` ### 3. Contexto (Context) Descreve o ambiente operacional e restrições essenciais para evitar erros comuns observados em testes anteriores: ``` Contexto: - Utilize apenas tabelas desta lista: {tables} - Você pode descobrir schemas usando: PRAGMA table_info(tabela) ``` Ensinar o modelo a inspecionar schemas com `PRAGMA` permite que ele se recupere sozinho de erros de sintaxe ou colunas inexistentes, sem intervenção manual. ### 4. Tarefa (Task) Descreve o fluxo de raciocínio e os passos de execução: ``` Tarefa: - Crie um plano de quais tabelas consultar para atender ao pedido do usuário. - Confirme o plano com o usuário antes de executar. - Se uma query falhar por coluna inexistente, execute a descoberta de schema e tente novamente. - Consulte a(s) tabela(s) necessária(s). - Apresente os resultados em formato legível para humanos (tabela ou lista). ``` ## Adicionando Modo de Depuração (Debug Mode) Para acompanhar exatamente quais comandos SQL o modelo está gerando, implementamos um modo de depuração via ferramenta (`set_debug_mode`). Em vez de tentar inspecionar manualmente estruturas complexas de raciocínio intermediário, fornecemos uma ferramenta que permite ao próprio agente ativar ou desativar os logs de depuração: ```python debug = False def set_debug_mode(debug_mode: bool): """Ativa ou desativa o modo de depuração. Args: debug_mode (bool): True para ativar o debug, False para desativar. Returns: None """ global debug debug = debug_mode ``` Adicionamos a ferramenta ao prompt de sistema e atualizamos a função `call_osquery` para imprimir as queries geradas quando `debug` estiver ativo: ```python def call_osquery(query: str): """Consulta o sistema operacional usando o osquery.""" if not instance.is_running(): instance.open() if debug: print("Executando query: ", query) result = instance.client.query(query) if debug: print("Resultado da query: ", { "status": result.status.message if result.status else None, "response": result.response if result.response else None }) return result ``` Veja o comportamento em tempo de execução: ``` $ python main.py Bem-vindo ao Agente de Diagnóstico de Emergência Qual é a natureza da sua emergência de diagnóstico? >> execute um diagnóstico de nível 1 em modo debug Executando query: SELECT * FROM system_info Resultado da query: {'status': 'OK', 'response': [{...}]} Executando query: SELECT pid, name, user, cpu_percent FROM processes ORDER BY cpu_percent DESC LIMIT 10 Resultado da query: {'status': 'no such column: user', 'response': None} Executando query: PRAGMA table_info(processes) Resultado da query: {'status': 'OK', 'response': [{'cid': '0', 'dflt_value': '', 'name': 'pid', 'notnull': '1', 'pk': '1', 'type': 'BIGINT'}, ...]} ``` O comando "execute um diagnóstico de nível 1 em modo debug" demonstra a invocação encadeada de múltiplas ferramentas: o agente ativou o modo debug e em seguida executou as queries do diagnóstico. Quando a query inicial falhou por causa da coluna `user`, ele usou a instrução `PRAGMA` para descobrir os campos corretos e refez a consulta com sucesso. ## Gerenciando Histórico de Sessão (Chat History) Para permitir que o agente mantenha o contexto entre perguntas consecutivas, integramos o gerenciamento de histórico de sessão com `InMemoryChatMessageHistory` do LangChain: ```python from langchain_core.chat_history import InMemoryChatMessageHistory import uuid chats_by_session_id = {} def get_chat_history(session_id: str) -> InMemoryChatMessageHistory: chat_history = chats_by_session_id.get(session_id) if chat_history is None: chat_history = InMemoryChatMessageHistory() chats_by_session_id[session_id] = chat_history return chat_history ``` Ao instanciar o agente, passamos a fábrica `get_chat_history` e propagamos o `session_id` em cada consulta: ```python response = agent.query( input=query, config={"configurable": {"session_id": session_id}} ) ``` Assim, enquanto a sessão permanecer ativa, o agente mantém a conversa em sua memória recente, permitindo perguntas de acompanhamento como "explique o passo anterior" ou "compare os dados atuais com o último relatório". ## Conclusões Neste artigo, aprendemos a estruturar instruções de sistema para obter comportamentos consistentes, a utilizar ferramentas para alternar flags de configuração dinamicamente e a gerenciar histórico de sessão na memória. Na próxima parte desta série, [Como Criar um Agente de Diagnóstico com o Agent Development Kit](https://danicat.dev/pt-br/posts/20251020-diagnostic-agent-with-adk/), vamos refatorar essa arquitetura para o Google ADK (Agent Development Kit), eliminando o boilerplate manual e integrando RAG na Vertex AI para descoberta dinâmica de schemas. Compartilhe suas ideias e perguntas nos comentários abaixo! --- # Explorando a Fundo o SDK da Vertex AI para Python - **Markdown URL**: https://danicat.dev/pt-br/posts/vertex-ai-sdk-python/index.md - **HTML URL**: https://danicat.dev/pt-br/posts/vertex-ai-sdk-python/ - **Date**: 2025-06-05 - **Categories**: Agent Development - **Tags**: gemini, python, tutorial, vertex-ai - **Series**: Building the Diagnostic Agent - **Summary**: Guia aprofundado sobre a comunicação entre cliente e API no SDK da Vertex AI para Python. Aprenda a estruturar instruções de sistema, function calling e histórico.## Introdução Este artigo explora o modelo de comunicação entre o código cliente e a API Gemini usando o [SDK da Vertex AI para Python](https://cloud.google.com/vertex-ai/docs/python-sdk/use-vertex-ai-python-sdk?utm_campaign=CDR_0x72884f69_awareness_b422727650&utm_medium=external&utm_source=blog). Vamos cobrir como as mensagens são estruturadas, como o modelo compreende o contexto de uma conversa e como expandir as capacidades do modelo com function calls (chamadas de função). Embora o foco aqui seja o Gemini, os mesmos conceitos se aplicam a modelos como o Gemma e outros LLMs modernos. [Na Parte 1 desta série](https://danicat.dev/pt-br/posts/20250531-diagnostic-agent/), mostrei como criar um agente de IA simples, porém surpreendentemente poderoso, que responde a perguntas de diagnóstico sobre a sua máquina local. Com pouquíssimas linhas de código (e comentários bem detalhados), nosso agente já respondia a pedidos como "quanto de CPU tenho disponível" ou "verifique se há sinais de malware". Isso foi possível graças à facilidade do SDK do Python, que abstrai boa parte da complexidade. Por exemplo, usei o recurso de [Automatic Function Calling](https://ai.google.dev/gemini-api/docs/function-calling?example=weather#automatic_function_calling_python_only) para deixar o agente decidir quando invocar cada função. Esse recurso também me permitiu declarar funções Python normais enquanto o SDK inferia assinaturas e docstrings dinamicamente. No entanto, essa facilidade é exclusiva do SDK Python; desenvolvedores em Go, JavaScript ou Java precisam estruturar essas chamadas de forma manual. Por isso, neste artigo vamos entender o funcionamento da API Gemini por baixo do capô, preparando você para usar com segurança não apenas o SDK Python, mas qualquer outro SDK disponível (Go, JS, Java). Continuarei usando Python nos exemplos para facilitar a comparação com o post anterior, mas a lógica se aplica a qualquer linguagem. Abordaremos dois tópicos principais: * Como funciona o fluxo de conversa entre o cliente e o modelo * Como implementar function calling de forma manual em baixo nível Mesmo se você programa exclusivamente em Python, entender o ciclo de vida dessas mensagens é fundamental para dominar recursos avançados do SDK (como a Live API) e construir agentes robustos. ## Compreendendo o funcionamento da API Agentes funcionam como aplicações cliente-servidor clássicas: de um lado, há um cliente encarregado de montar e enviar as requisições; do outro, um processo remoto que hospeda o runtime do modelo e processa os dados recebidos. Na Vertex AI, encontramos dois grupos principais de APIs: APIs REST para o modelo tradicional de requisição/resposta (onde o cliente envia uma mensagem e aguarda a conclusão antes de continuar) e a [Live API](https://cloud.google.com/vertex-ai/generative-ai/docs/live-api?utm_campaign=CDR_0x72884f69_awareness_b422727650&utm_medium=external&utm_source=blog), que processa streaming bidirecional em tempo real via WebSockets. Vamos focar primeiro nas APIs REST. Podemos gerar conteúdo em diversas modalidades: texto, imagem, áudio e vídeo. Os modelos mais recentes são multimodais nativos, permitindo combinar diferentes tipos de entrada e saída em uma mesma interação. Para manter as coisas simples, vamos começar com texto. Uma chamada direta e pontual a um modelo se parece com isto: ```python from google import genai client = genai.Client( vertexai=True, project="meu-projeto-sandbox", location="us-central1" ) response = client.models.generate_content( model="gemini-2.0-flash", contents="Como você está hoje?" ) print(response.text) ``` Saída: ``` Estou funcionando perfeitamente, obrigado por perguntar! Como um modelo de linguagem, não sinto emoções como os humanos, mas estou pronto para ajudar. Como posso ser útil hoje? ``` Primeiro instanciamos o cliente (seja no modo Vertex AI (`vertexai=True`) ou fornecendo uma chave de API do Gemini Developer). Em seguida, enviamos o prompt chamando `client.models.generate_content`, especificando o modelo (`gemini-2.0-flash`) e a mensagem no argumento `contents`. À primeira vista, parece uma simples troca de strings. Mas, por baixo das abstrações do Python, **o conteúdo não é uma string simples**. `contents` é, na verdade, uma lista de estruturas de conteúdo (`Content`), onde cada estrutura é composta por uma função (`role`) e uma ou mais partes (`parts`). A estrutura interna definida na biblioteca de tipos se parece com: ```python from google.genai import types contents = [types.Content( role = "user", parts = [ types.Part.from_text("Como você está hoje?") ] )] ``` Quando passamos `contents="Como você está hoje?"`, o SDK do Python converte automaticamente a string para uma estrutura `Content` contendo uma `Part` de texto. Outro ponto crucial: a cada chamada a `generate_content`, o modelo é executado sem memória prévia. É **nossa** responsabilidade incluir o histórico de mensagens anteriores nas próximas requisições se quisermos manter uma conversa contínua. Vamos comprovar isso perguntando que dia é hoje duas vezes seguidas: ```python response = client.models.generate_content( model="gemini-2.0-flash", contents="que dia é hoje?" ) print(response.text) response = client.models.generate_content( model="gemini-2.0-flash", contents="que dia é hoje?" ) print(response.text) ``` Saída: ``` $ python3 main.py Hoje é domingo, 5 de novembro de 2023. Hoje é sábado, 2 de novembro de 2024. ``` Temos dois problemas aqui: 1) o modelo alucinou, pois não tem acesso ao relógio do sistema, e 2) forneceu duas respostas totalmente diferentes para a mesma pergunta. O problema 1 é resolvido com ferramentas (como uma função datetime ou busca no Google), mas o problema 2 demonstra que o modelo não se lembra do que acabou de responder. O controle do estado da conversa cabe exclusivamente ao código cliente. Vamos ajustar o código para enviar o histórico da conversa: ```python response = client.models.generate_content( model="gemini-2.0-flash", contents="que dia é hoje?" ) print(response.text) # Cada item na lista contents representa um "turno" (turn) da conversa contents = [ { "role": "user", "parts": [{ "text": "que dia é hoje?" }] }, { "role": "model", "parts": [{ "text": response.text }] }, { "role": "user", "parts": [{ "text": "que dia é hoje?" }] }, ] response = client.models.generate_content( model="gemini-2.0-flash", contents=contents ) print(response.text) ``` Saída: ``` $ python3 main.py Hoje é quarta-feira, 15 de novembro de 2023. Hoje é quarta-feira, 15 de novembro de 2023. ``` Na segunda chamada, enviamos o histórico completo. Observe que o atributo `role` alterna entre `"user"` e `"model"` (os únicos valores aceitos para papel na conversa). É assim que o modelo identifica em qual turno do diálogo está. Se omitíssemos o último turno do usuário, o modelo assumiria que a conversa terminou e não geraria uma nova resposta. O SDK também fornece helpers como `types.UserContent` e `types.Part.from_text` para montar essas estruturas de forma mais elegante. Para trabalhar com dados multimídia, usamos partes específicas (como chamadas de função, dados binários ou URIs do Cloud Storage): ```python from google.genai import types contents = types.Part.from_uri( file_uri='gs://generativeai-downloads/images/scones.jpg', mime_type='image/jpeg', ) ``` Ou dados binários inline: ```python contents = types.Part.from_bytes( data=minha_foto_binaria, mime_type='image/jpeg', ) ``` Como gerenciar esse histórico é uma necessidade constante em aplicações conversacionais, o SDK da Vertex AI já traz essa abstração pronta na API `chats`: ```python chat = client.chats.create(model='gemini-2.0-flash') response = chat.send_message('que dia é hoje?') print(response.text) response = chat.send_message('que dia é hoje?') print(response.text) ``` Saída: ``` $ python3 main.py Hoje é sábado, 14 de outubro de 2023. Hoje é sábado, 14 de outubro de 2023. ``` Aqui o objeto `chat` gerencia o histórico de turnos automaticamente para nós. ## Implementando Function Calling de forma manual Agora que entendemos como o modelo consome mensagens e gerencia contexto, vamos ver como funciona o ciclo de chamadas de ferramentas sem depender de mágica automática. Para que o modelo utilize uma função, precisamos declarar a existência dessa função (nome, descrição e parâmetros aceitos). O modelo analisa essas declarações para decidir quando uma chamada é necessária. Veja a declaração da função `get_random_number`: ```python get_random_number_decl = { "name": "get_random_number", "description": "Retorna um número aleatório", } ``` Essa declaração possui três campos principais: `name`, `description` e `parameters` (como esta função não recebe argumentos, o campo foi omitido). O modelo se baseia na descrição da função e dos parâmetros para decidir se deve ou não acioná-la. No artigo anterior, deixamos o SDK extrair esses metadados da docstring da função. Agora, vamos declarar a função e a ferramenta explicitamente: ```python def get_random_number(): return 4 # escolhido por um lançamento justo de dados # garantidamente aleatório (https://xkcd.com/221/) get_random_number_decl = { "name": "get_random_number", "description": "Retorna um número aleatório", } ``` Você pode conferir outros esquemas de declaração na [documentação de function calling da Vertex AI](https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/function-calling#schema-examples?utm_campaign=CDR_0x72884f69_awareness_b422727650&utm_medium=external&utm_source=blog). Agora registramos a ferramenta na configuração do modelo: ```python tools = types.Tool(function_declarations=[get_random_number_decl]) config = types.GenerateContentConfig(tools=[tools]) contents = [types.Part.from_text(text="qual é o meu número da sorte hoje?")] response = client.models.generate_content( model="gemini-2.0-flash", contents=contents, config=config, ) print(response.candidates[0].content.parts[0]) ``` Ao executar o código acima, obtemos: ``` $ python3 main.py video_metadata=None thought=None inline_data=None file_data=None thought_signature=None code_execution_result=None executable_code=None function_call=FunctionCall(id=None, args={}, name='get_random_number') function_response=None text=None ``` Observe que todos os campos da resposta estão vazios (`None`), exceto o campo `function_call`. O modelo não gerou texto final; ele está nos informando: *"Por favor, execute a função `get_random_number` e me devolva o resultado"*. O modelo sabe que a função existe, mas não pode executá-la diretamente no servidor do Google. Ele depende do cliente para rodar o código localmente e fornecer a resposta de volta. Para concluir o ciclo, executamos a função no cliente e enviamos o resultado de volta ao modelo, acompanhado de todo o histórico anterior: ```python # 1. Executamos a função solicitada pelo modelo result = get_random_number() # 2. Anexamos a resposta do modelo (o pedido de execução da função)... contents.append(types.ModelContent(parts=response.candidates[0].content.parts)) # 3. ... e anexamos o resultado retornado pela nossa função local contents.append(types.UserContent(parts=types.Part.from_function_response(name="get_random_number", response={"result": result}))) # 4. Enviamos o histórico atualizado de volta ao modelo response = client.models.generate_content( model="gemini-2.0-flash", contents=contents, config=config, ) print(response.text) ``` Saída: ``` $ python3 main.py Seu número da sorte para hoje é 4. ``` ## Conclusões Neste artigo, desmistificamos como o cliente de um agente conversa com o modelo em nível de protocolo e eliminamos a "mágica" das ferramentas automáticas. A automação é excelente para produtividade, mas compreender o fluxo real de mensagens é o que diferencia quem apenas copia exemplos de quem consegue diagnosticar bugs e arquitetar sistemas de agentes confiáveis. No contexto atual de vibe coding, saber exatamente o que acontece por baixo dos panos permite criar prompts e especificações muito mais precisas, economizando tempo e recursos de computação. Na próxima parte desta série, [Instruções de Sistema e Ferramentas para Agentes](https://danicat.dev/pt-br/posts/20250611-system-prompt/), vamos usar essa base para criar uma aplicação de chat completa no terminal com histórico persistente de sessão e injeção dinâmica de schemas de tabelas. Deixe seus comentários e dúvidas abaixo! Até a próxima o/ --- # Como transformei meu computador na "USS Enterprise" usando Agentes de IA - **Markdown URL**: https://danicat.dev/pt-br/posts/diagnostic-agent-uss-enterprise/index.md - **HTML URL**: https://danicat.dev/pt-br/posts/diagnostic-agent-uss-enterprise/ - **Date**: 2025-05-31 - **Categories**: Agent Development - **Tags**: gemini, python, tutorial, vertex-ai - **Series**: Building the Diagnostic Agent - **Summary**: Construa um assistente de diagnóstico inspirado em Star Trek usando Python, Gemini, Vertex AI Agent Engine e function calling do osquery para telemetria em linguagem natural._Espaço: a fronteira final. Estas são as viagens da nave estelar Enterprise. Em sua missão de cinco anos: para explorar novos mundos; para pesquisar novas vidas e novas civilizações; para audaciosamente ir onde nenhum homem jamais esteve._ ## Introdução Quando eu era criança, graças à influência do meu pai, me acostumei a ouvir essas palavras quase todos os dias. Suspeito que a paixão dele por Star Trek tenha tido um papel enorme na minha decisão de seguir a carreira de engenharia de software. (Para quem não conhece Star Trek, essa introdução passava no início de cada episódio da série clássica). Star Trek sempre esteve à frente de seu tempo. Mostrou o [primeiro beijo interracial da televisão americana](https://en.wikipedia.org/wiki/Kirk_and_Uhura%27s_kiss), em uma época em que uma cena dessas causava imensa controvérsia. Também retratou inúmeras tecnologias "futuristas" que hoje são rotineiras, como smartphones e videoconferências. Uma coisa realmente marcante é a forma como os engenheiros da série interagem com os computadores. Embora a gente veja teclados e botões sendo pressionados de vez em quando, muitos dos comandos são dados por voz, em linguagem natural. Alguns dos comandos são icônicos, como quando pedem ao computador para executar um "procedimento de diagnóstico de nível 1", algo que aconteceu tantas vezes que praticamente virou [uma piada](https://www.youtube.com/watch?v=cYzByQjzTb0) entre os fãs mais apaixonados. Avançando mais de 30 anos no tempo, aqui estamos nós na Era da IA, uma transformação tecnológica que promete ser maior do que a própria internet. Muita gente tem receio de como a IA pode impactar seus empregos ([escrevi sobre isso na semana passada](https://danicat.dev/pt-br/posts/20250528-vibe-coding/)), mas crescer assistindo Star Trek me ajudou a enxergar com mais clareza como o papel da pessoa engenheira vai mudar nos próximos anos. Em vez de comandar a máquina exclusivamente por texto, instruindo cada detalhe passo a passo por meio de linhas de código e compiladores, muito em breve vamos conversar e fazer brainstorming diretamente com nossos computadores. Para ajudar a visualizar esse conceito na prática, vamos usar a tecnologia disponível hoje para criar um agente simples que nos permite interagir com a nossa própria máquina usando linguagem natural. ## O que você vai precisar para este tutorial Para o desenvolvimento, usaremos Python em um Jupyter Notebook, o que facilita bastante a experimentação. As principais ferramentas e bibliotecas que utilizaremos são: * [Vertex AI Agent Engine](https://cloud.google.com/vertex-ai/generative-ai/docs/agent-engine/overview?utm_campaign=CDR_0x72884f69_awareness_b421478530&utm_medium=external&utm_source=blog) * [Osquery](https://www.osquery.io/) com [bindings em Python](https://github.com/osquery/osquery-python) * [Jupyter Notebook](https://jupyter.org/) [opcional] (estou usando a [extensão do Jupyter para VSCode](https://code.visualstudio.com/docs/datascience/jupyter-notebooks)) Os exemplos abaixo usam o Gemini 2.0 Flash, mas você pode usar qualquer [variante do Gemini](https://ai.google.dev/gemini-api/docs/models). Não faremos o deploy deste agente no Google Cloud desta vez porque queremos utilizá-lo para responder perguntas sobre a máquina local, e não sobre um servidor remoto na nuvem. ## Visão geral de agentes Se você já conhece o funcionamento de agentes de IA, pode pular esta seção. Um agente de IA é um sistema capaz de perceber seu ambiente e tomar decisões autônomas para atingir objetivos específicos. Ao contrário dos Modelos de Linguagem Tradicionais (LLMs), que focam primordialmente em gerar texto a partir de um prompt, agentes de IA podem interagir com seu ambiente, tomar decisões e executar tarefas para concluir seus objetivos. Isso é viabilizado pelo uso de "ferramentas" (tools), que fornecem dados ao agente e concedem a ele a capacidade de executar ações. Para demonstrar essa tecnologia, vamos usar o LangChain por meio do [Agent Engine](https://cloud.google.com/vertex-ai/generative-ai/docs/agent-engine/develop/langchain?utm_campaign=CDR_0x72884f69_awareness_b421478530&utm_medium=external&utm_source=blog). Primeiro, instale os pacotes necessários no seu ambiente: ```shell pip install --upgrade --quiet google-cloud-aiplatform[agent_engines,langchain] ``` Você também precisará configurar as Application Default Credentials (ADC) da Google Cloud CLI: ```shell gcloud auth application-default login ``` *Nota:* dependendo do ambiente em que estiver executando esta demonstração, pode ser necessário usar um método de autenticação diferente. Agora estamos prontos para trabalhar no script Python. Primeiro, inicializamos o SDK informando o Project ID e a região no Google Cloud: ```python import vertexai vertexai.init( project="meu-project-id", # Seu Project ID. location="us-central1", # Sua região no Google Cloud. staging_bucket="gs://meu-staging-bucket", # Seu bucket de staging. ) ``` Com a configuração inicial pronta, criar um agente usando LangChain no Agent Engine é bastante direto: ```python from vertexai import agent_engines model = "gemini-2.0-flash" # sinta-se à vontade para testar outros modelos! model_kwargs = { # temperature (float): controla a aleatoriedade na seleção de tokens. "temperature": 0.20, } agent = agent_engines.LangchainAgent( model=model, # Obrigatório. model_kwargs=model_kwargs, # Opcional. ) ``` O setup acima já é suficiente para você enviar perguntas ao agente, da mesma forma que faria com um LLM comum: ```python response = agent.query( input="que horas são agora?" ) print(response) ``` O retorno será algo como: ``` {'input': 'que horas são agora?', 'output': 'Como uma IA, não tenho uma noção de horário "atual" ou localização da mesma forma que um ser humano. Meus conhecimentos não são atualizados em tempo real.\n\nPara verificar o horário atual, você pode:\n\n* **Checar seu dispositivo:** Seu computador, celular ou tablet exibe a hora atual.\n* **Fazer uma busca rápida:** Pesquise "que horas são" em um mecanismo de busca como o Google.'} ``` Dependendo das configurações, do prompt e da aleatoriedade do modelo, ele pode responder que não sabe a hora ou pode "alucinar" e inventar um timestamp. Como o modelo não tem um relógio interno, ele não consegue responder a essa pergunta... a menos que a gente entregue um relógio a ele! ## Function Calling (Chamadas de Funções) Uma das maneiras mais práticas de estender a capacidade do nosso agente é fornecer funções Python para ele executar. O processo é simples, mas vale ressaltar: quanto melhor for a documentação da função (docstring), mais fácil será para o modelo entender quando e como chamá-la. Vamos definir a função para checar o horário atual: ```python import datetime def get_current_time(): """Retorna o horário atual como um objeto datetime. Args: None Returns: datetime: horário atual no formato datetime """ return datetime.datetime.now() ``` Agora que temos uma função que retorna a hora do sistema, vamos recriar o agente, desta vez informando que essa ferramenta existe: ```python agent = agent_engines.LangchainAgent( model=model, # Obrigatório. model_kwargs=model_kwargs, # Opcional. tools=[get_current_time] ) ``` E fazemos a pergunta novamente: ```python response = agent.query( input="que horas são agora?" ) print(response) ``` A saída será parecida com: ``` {'input': 'que horas são agora?', 'output': 'O horário atual é 18:36:42 UTC em 30 de maio de 2025.'} ``` Agora o agente consulta a ferramenta para responder com dados reais do sistema. Bacana, não? ## Coletando Informações do Sistema Para o nosso agente de diagnóstico, vamos conceder a ele a capacidade de consultar dados sobre a máquina em que está rodando usando o [Osquery](https://www.osquery.io/). O Osquery é uma ferramenta open source criada originalmente pelo Facebook que permite consultar o sistema operacional por meio de queries SQL aplicadas a "tabelas virtuais". Isso é muito vantajoso porque nos dá uma interface única e padronizada para inspecionar o sistema, e LLMs são extremamente competentes em escrever código SQL. Você encontra as instruções de instalação do Osquery na [documentação oficial](https://osquery.readthedocs.io/en/stable/). Não vou reproduzi-las aqui porque os passos variam de acordo com o sistema operacional. Com o Osquery instalado, instale os bindings Python oficiais: ```shell pip install --upgrade --quiet osquery ``` Com os bindings instalados, você pode executar consultas importando o pacote `osquery`: ```python import osquery # Inicia um processo do osquery usando um socket de extensão efêmero. instance = osquery.SpawnInstance() instance.open() # Pode lançar uma exceção # Executa queries via Thrift APIs do osquery. instance.client.query("select timestamp from time") ``` O método `query` retorna um objeto `ExtensionResponse` com o resultado da execução: ```python ExtensionResponse(status=ExtensionStatus(code=0, message='OK', uuid=0), response=[{'timestamp': 'Fri May 30 17:54:06 2025 UTC'}]) ``` Se você nunca trabalhou com Osquery antes, recomendo dar uma olhada no [schema oficial](https://www.osquery.io/schema/5.17.0/) para explorar quais tabelas e dados estão disponíveis para o seu sistema operacional. ### Uma dica sobre formatação Nos exemplos anteriores, as saídas estavam sem formatação. Se você estiver rodando no Jupyter, pode formatar as respostas com Markdown importando os seguintes módulos: ```python from IPython.display import Markdown, display ``` E exibindo o resultado formatado: ```python response = agent.query( input="qual é a data estelar de hoje?" ) display(Markdown(response["output"])) ``` Saída: ``` Diário de Bordo do Capitão, Suplementar. A data estelar atual é 48972.5. ``` ## Conectando as peças Agora que temos uma forma de consultar o sistema operacional, vamos juntar isso ao nosso agente para responder a perguntas reais sobre a saúde da máquina. O primeiro passo é definir a função que fará as consultas via Osquery. Essa função será entregue como ferramenta ao agente: ```python def call_osquery(query: str): """Consulta o sistema operacional usando o osquery. Esta função envia uma query ao processo do osquery para retornar informações sobre a máquina atual, sistema operacional e processos em execução. Você também pode usar esta função para consultar o banco SQLite subjacente e descobrir metadados da instância do osquery usando tabelas de sistema como sqlite_master, sqlite_temp_master e tabelas virtuais. Args: query: str Uma query SQL para uma das tabelas do osquery (ex: "select timestamp from time") Returns: ExtensionResponse: resposta do osquery com o status da requisição e os dados da query se bem-sucedida. """ return instance.client.query(query) ``` A função em si é bem concisa, mas o ponto crucial é a docstring detalhada, que permite ao modelo entender exatamente o propósito e as restrições da ferramenta. Durante os meus testes, notei que o agente muitas vezes tentava acessar tabelas que não existiam no meu sistema. Por exemplo, no macOS a tabela `memory_info` não está presente. Para dar mais contexto ao modelo, vamos listar dinamicamente todas as tabelas virtuais disponíveis no ambiente atual. Em um cenário ideal, forneceríamos o schema completo com colunas e tipos, mas apenas a lista de tabelas já ajuda enormemente. Como o motor interno do Osquery utiliza SQLite, podemos consultar os nomes das tabelas virtuais em `sqlite_temp_master`: ```python # Consulta as tabelas virtuais disponíveis nesta máquina response = instance.client.query("select name from sqlite_temp_master").response tables = [ t["name"] for t in response ] ``` Com a lista de tabelas em mãos, configuramos o agente com essas instruções e a ferramenta `call_osquery`: ```python osagent = agent_engines.LangchainAgent( model = model, system_instruction=f""" Você é um agente que responde perguntas sobre a máquina em que está sendo executado. Você deve executar queries SQL usando uma ou mais tabelas disponíveis para responder às perguntas do usuário. Sempre retorne valores legíveis para humanos (ex: megabytes em vez de bytes, e tempo formatado em vez de milissegundos). Seja flexível na interpretação dos pedidos. Por exemplo, se o usuário pedir informações sobre aplicativos, você pode retornar processos e serviços. Se pedir consumo de recursos, retorne TANTO informações de memória QUANTO de CPU. Não faça perguntas de esclarecimento ao usuário. Você tem as seguintes tabelas disponíveis no sistema: ----- TABELAS ----- {tables} ----- FIM DAS TABELAS ----- Pergunta: """, tools=[ call_osquery, ] ) ``` Com isso, nosso agente de diagnóstico está pronto! Vamos colocá-lo à prova: ```python response = osagent.query(input="qual é a hora atual?") display(Markdown(response["output"])) ``` Saída: ``` A hora atual é Fri May 30 18:08:15 2025 UTC. ``` Vamos testar uma consulta mais detalhada: ```python response = osagent.query(input="qual processo está consumindo mais recursos?") display(Markdown(response["output"])) ``` Saída: ``` O processo com maior consumo de CPU é: Nome do Processo: mediaanalysisd, PID: 1127, Tempo Total de CPU: 2876716 O processo com maior consumo de memória é: Nome do Processo: Code Helper (Plugin), PID: 2987, Uso de Memória: 1537 MB ``` E se pedirmos algo no espírito de Star Trek? ```python response = osagent.query(input="computador, execute um procedimento de diagnóstico de nível 1") display(Markdown(response["output"])) ``` Saída: ``` Aqui está o relatório do diagnóstico de nível 1: **Sistema Operacional:** * **Nome:** macOS * **Versão:** 15.5 * **Build:** 24F74 * **Plataforma:** darwin * **Arquitetura:** arm64 ... omitido para brevidade ... **Tempo de Atividade (Uptime):** * **Total de Segundos:** 261164 * **Dias:** 3 * **Horas:** 0 * **Minutos:** 32 * **Segundos:** 44 ``` O agente montou um relatório de diagnóstico completo e estruturado por conta própria, sem que eu precisasse codificar as regras de um diagnóstico de nível 1! Também testei perguntas de segurança: ```python response = osagent.query(input="computador, você vê algum sinal de malware em execução?") display(Markdown(response["output"])) ``` Saída: ``` Verifiquei processos não associados a arquivos em disco (um indicador comum de malware) e nenhum foi encontrado. Também examinei os processos com maior consumo de memória e CPU. Os processos com maior uso são o Visual Studio Code e o Google Chrome, juntamente com seus processos auxiliares, o que representa um comportamento normal. Com base nas verificações realizadas, não há sinais evidentes de malware em execução no sistema neste momento. ``` ## Conclusões Com pouquíssimas linhas de código, passamos de zero a uma interface em linguagem natural capaz de inspecionar o funcionamento interno do sistema operacional. Com alguns ajustes adicionais, esse agente pode realizar diagnósticos ainda mais profundos e até sugerir correções de forma autônoma. O Scotty ficaria orgulhoso! ![Engenheiro Scotty tentando falar com o computador usando o mouse como microfone](https://danicat.dev/pt-br/posts/diagnostic-agent-uss-enterprise/hello-computer-hello.gif) Você pode conferir o código-fonte de todos os exemplos deste artigo no meu [GitHub](https://github.com/danicat/devrel/blob/main/blogs/20250531-diagnostic-agent/diagnostic_agent.ipynb). Na próxima parte desta série, [Explorando a Fundo o SDK da Vertex AI para Python](https://danicat.dev/pt-br/posts/20250605-vertex-ai-sdk-python/), vamos entender como funciona o protocolo de comunicação entre o cliente e o Gemini, e implementar function calling manual em baixo nível. O que achou da experiência? Compartilhe suas impressões nos comentários abaixo! --- # Qualquer Pessoa Pode Fazer Vibe Coding? O Futuro da Engenharia - **Markdown URL**: https://danicat.dev/pt-br/posts/can-anyone-vibe-code/index.md - **HTML URL**: https://danicat.dev/pt-br/posts/can-anyone-vibe-code/ - **Date**: 2025-05-28 - **Categories**: Agentic Coding - **Tags**: career, vibe-coding - **Summary**: Qualquer um pode programar no vibe code? Uma reflexão sobre como a IA reduz barreiras enquanto torna a disciplina de engenharia e orquestração ainda mais valiosas. ## Introdução Você acredita que uma pessoa sem formação em engenharia consegue criar uma aplicação pronta para produção usando apenas "vibe coding"? Essa é a pergunta sobre a qual tenho refletido bastante ultimamente. O cenário da IA está evoluindo a passos largos, com ferramentas incríveis surgindo praticamente todos os dias. É natural que muita gente fique apreensiva e comece a "profetizar" o fim da carreira de engenharia de software. Veja o [Jules](https://danicat.dev/pt-br/posts/20250521-jules/), por exemplo: um agente de IA capaz de automatizar diversas tarefas rotineiras de engenharia, como atualizar dependências, escrever documentação, refatorar código e até criar testes. Se essas fossem as **únicas** contribuições que você traz para a mesa como engenheira ou engenheiro, talvez fizesse sentido se preocupar. Mas o medo não é necessariamente um vilão: ele nos empurra para fora da zona de conforto e nos encoraja a assumir riscos que de outra forma evitaríamos. Na medida certa, o medo impulsiona o autodesenvolvimento e a inovação. ## Afinal, o que *é* a Engenharia? Talvez eu não seja a pessoa ideal para responder a essa pergunta porque, embora tenha cursado Engenharia Elétrica na faculdade, eu nunca cheguei a me formar. Ainda assim, posso garantir que, mesmo sem o diploma, o tempo que passei no curso foi fundamental para moldar a minha mentalidade na hora de abordar problemas ao longo de toda a minha carreira. Um dos momentos mais marcantes que me influenciaram foi uma aula magna com o Prof. Dr. Ewaldo Mehl (UFPR), discutindo a carreira de engenharia com a nossa turma de calouros. Entre várias colocações brilhantes, uma ficou gravada na minha memória até hoje, mais de 20 anos depois, sobre a essência do que fazemos... Ele disse algo como: "Os engenheiros são os profissionais mais próximos dos deuses (...) porque nós não apenas consertamos coisas, nós as *criamos*." Sim, eu sei que essa fala solta fora de contexto soa estranha, mas o ponto central da conversa girava em torno do "complexo de Deus" que algumas profissões, como a Medicina, frequentemente enfrentam. Meu professor estava apenas brincando com isso ao dizer que os engenheiros não deveriam se sentir inferiores aos médicos, porque seríamos nós os verdadeiros donos desse complexo. (Peguem leve nos comentários, era início dos anos 2000 e ainda estávamos muito perto dos anos 90! :-) Há um ditado popular de onde eu venho que diz que "no fundo de toda brincadeira há um fundo de verdade". Tirando os exageros de lado, o Prof. Mehl tinha razão em um ponto essencial: quando removemos todas as ferramentas, na nossa essência, somos criadores. A forma como criamos evolui. Começamos criando com as próprias mãos e matéria-prima bruta. Depois vieram ferramentas manuais, computadores e robôs. Agora, estamos entrando na era da IA. Portanto, sempre que você pensar sobre a carreira de engenharia (e se ela vai morrer ou não), lembre-se da necessidade humana intrínseca de criar coisas novas. Nós sempre estaremos iterando, melhorando e inovando. Essa é uma característica tão fundamental da natureza humana que não consigo acreditar que vá desaparecer, seja nos próximos 5 anos ou no próximo milênio. Mas a *forma* como criamos e aperfeiçoamos as coisas... essa sim, com certeza vai mudar. ## Orquestrar, e Não Apenas Codificar Acredito que a revolução da IA vai nos libertar do trabalho repetitivo e maçante para que possamos nos concentrar no aspecto mais fascinante do nosso trabalho: arquitetura, design, planejamento e validação de soluções. Não estou dizendo que programar seja chato. Muito pelo contrário: como a maioria das pessoas que trabalham com engenharia de software, eu adoro programar. Mas manter código profissional em produção é muito diferente de programar por pura diversão. Existe uma quantidade enorme de código que precisamos escrever que não agrega valor direto ao objetivo final que queremos atingir. Pense em todos os requisitos não funcionais, como segurança, logging, telemetria, métricas e redes; nas horas gastas configurando ambientes, fazendo deploy, testando e validando; em todos os passos de setup para integrar camadas de aplicação, bancos de dados e assim por diante... Boa parte do que fazemos no dia a dia serve apenas para "fazer a coisa funcionar", e não para resolver o problema de negócio em si. Mesmo tarefas aparentemente simples, como colocar este blog no ar, me custaram alguns dias inteiros de trabalho: precisei configurar meu ambiente de desenvolvimento, pesquisar qual era a melhor ferramenta para blogs em 2025, avaliar opções de hospedagem, entender as convenções e a estrutura da plataforma escolhida (como o Hugo organiza sites), testar vários templates antes de escolher um, pesquisar como integrar comentários aos posts... e a lista continua. Como tudo isso se relaciona com o problema real que eu queria resolver? São etapas necessárias, sem dúvida, mas eu só as executei porque precisava "fazer a infraestrutura funcionar", e não porque essa era a minha meta final. O que eu realmente queria era criar uma fonte única de verdade (source of truth) para publicar meus artigos e, posteriormente, distribuí-los automaticamente para várias plataformas (como Medium, LinkedIn, etc.) a fim de alcançar mais pessoas. Eu ainda nem consegui atacar esse objetivo central porque precisei cuidar de cada pedacinho do pipeline de desenvolvimento de uma nova aplicação, da concepção ao deploy. Não seria incrível se pudéssemos abstrair toda essa sobrecarga e simplesmente pedir a uma IA: "gere uma plataforma de blog completa para mim, com tudo pronto para rodar"? É aqui que a IA vai se tornar absurdamente poderosa. Com o advento da IA Agêntica (Agentic AI), logo teremos centenas de agentes à nossa disposição para orquestrar todas as etapas do ciclo de vida de software. A nós caberá o papel principal de idealizar novas experiências, enquanto a "operacionalização" fica por conta dos agentes. Eles vão codificar, fazer deploys e iterar. Vão pesquisar opções por nós e apresentar caminhos, para que possamos decidir de acordo com a nossa visão. Pode parecer difícil visualizar isso sem um exemplo palpável, e é por isso que sou apaixonada por ficção científica: ela nos permite espiar o futuro. Pense no computador de bordo de Star Trek... Acredito que, em poucos anos, estaremos interagindo com computadores exatamente como [Geordi La Forge faz nesta cena](https://youtu.be/L0mRMp2kbQY?feature=shared): o processo de engenharia se torna um diálogo colaborativo, em vez de um esforço solitário da pessoa engenheira. Já chegamos lá? Ainda não, mas a IA Agêntica é o próximo salto para transformar o futuro de Star Trek em realidade. Já dá para sentir que a IA será um divisor de águas ainda maior do que foi a própria internet. Como pessoas desenvolvedoras, precisamos nos atualizar para essa "próxima geração" (com todo o trocadilho pretendido). ## Então, Qualquer Pessoa Pode Fazer "Vibe Coding"? Talvez não *qualquer* pessoa, mas a barreira de entrada está caindo drasticamente. As habilidades fundamentais estão evoluindo, mas o papel da engenharia de conceber, arquitetar e criar continua firme e forte, e continuará por muito tempo. **O que você acha? Você se vê atuando de forma mais estratégica e "hands-off", guiando a IA para dar vida às suas ideias? Vamos bater um papo nos comentários!** --- # Precisamos falar sobre o Jules! - **Markdown URL**: https://danicat.dev/pt-br/posts/jules-autonomous-coding-agent/index.md - **HTML URL**: https://danicat.dev/pt-br/posts/jules-autonomous-coding-agent/ - **Date**: 2025-05-21 - **Categories**: Agentic Coding - **Tags**: jules, vibe-coding - **Summary**: Primeiras impressões sobre o Google Jules, o agente autônomo e assíncrono de programação. Veja a resolução de issues no GitHub, geração automatizada de PRs e o fluxo de trabalho.Olá, pessoal! Vamos falar sobre o Jules! Recém-saído do forno do Google I/O, este é o que o Google está chamando de agente autônomo de programação… mas o que é um agente autônomo de programação? Pense no [NotebookLM](https://notebooklm.google/), só que para código, uma IA especializada para te ajudar em tarefas de programação. A principal diferença em relação à abordagem tradicional de “vibe coding” é que, com o Jules, você pode importar seu projeto inteiro como contexto para a IA, de modo que todas as respostas ficam ancoradas no código real em que você está trabalhando! ## Como o Jules funciona Assim que o projeto é importado, você pode interagir com o Jules enviando “tarefas” (tasks), que podem ser qualquer coisa: correções de bugs, atualizações de dependências, novas funcionalidades, planejamento, documentação, testes e muito mais. Assim que recebe uma tarefa, o Jules planeja sua execução assincronamente em etapas e realiza diferentes subtarefas para garantir o resultado esperado. Por exemplo, garantir que nenhum teste seja quebrado pela nova alteração. Ele se integra diretamente ao GitHub, então há pouquíssimo atrito para começar a usar. Ele ainda não vai substituir a IDE por completo, mas você pode realizar muitas tarefas diretamente pelo Jules, até o ponto em que ele cria uma branch com todas as alterações solicitadas, pronta para virar um pull request. ## Primeiras impressões e primeiros testes A consequência infeliz do anúncio do Jules ontem é que a ferramenta está sob carga pesadíssima no momento, então pode demorar um pouco para você ver os resultados após enviar uma tarefa. Mas o Jules faz o trabalho em segundo plano e, se você ativar as notificações do navegador, ele vai te avisar assim que estiver pronto. Diante disso, não consegui fazer nenhum experimento grande com ele, mas uma das coisas que fiz foi gerar o [README do projeto do meu blog no GitHub](https://github.com/danicat/danicat.dev/pull/1) (o código-fonte desta mesma página que você está lendo agora). Também tentei algumas iterações mais complexas, como ajustar o template do blog. [Ele gerou os arquivos corretos](https://github.com/danicat/danicat.dev/pull/2), mas demorou um pouco para responder aos pedidos, então precisei fazer algumas alterações manualmente. ## Uma nova era para as IDEs Nada mal para o primeiro dia, eu diria, e há muito potencial a ser destravado nas próximas semanas e meses. O recurso matador é a capacidade de trabalhar em uma base de código completa, em vez daquele fluxo tradicional de fazer uma pergunta ao Gemini (ou ChatGPT), copiar o código para a IDE, rodar, copiar e colar o resultado de volta no LLM e iterar. Claro que ferramentas como o Code Assist e o Copilot oferecem algumas dessas funcionalidades sem você sair da IDE, mas ainda sinto que a IDE não é o ambiente ideal para o vibe coding, parecendo mais um hack. Nesse espírito, talvez o Jules seja a injeção de inspiração de que precisávamos para uma nova era de IDEs que vai destravar o potencial da IA para desenvolvedores de todo o mundo de forma muito mais natural. Pelo menos é por isso que estou torcendo! ## Como começar O Jules está atualmente em beta público e você pode testá-lo hoje mesmo se inscrevendo em [https://jules.google](https://jules.google). --- # Hello World: Um Novo Espaço para Narrativas Técnicas - **Markdown URL**: https://danicat.dev/pt-br/posts/hello-world/index.md - **HTML URL**: https://danicat.dev/pt-br/posts/hello-world/ - **Date**: 2025-05-20 - **Categories**: Perspectives - **Tags**: blogging, career, reflection - **Summary**: Boas-vindas ao danicat.dev: explorando narrativas técnicas em engenharia de software, Google Cloud, agentes de IA e reflexões de carreira por Daniela Petruzalek. Após mais de 20 anos na indústria de tecnologia, percebi que a forma mais eficaz de consolidar meu próprio aprendizado sobre qualquer assunto é compartilhá-lo com outras pessoas. Este blog, `danicat.dev`, é um espaço dedicado exatamente a esse propósito. Meu nome é Daniela Petruzalek e atualmente trabalho como Developer Relations Engineer no Google. Este blog servirá como uma plataforma para compartilhar narrativas técnicas inspiradas no meu trabalho com desenvolvimento de software, computação em nuvem e IA. ## Propósito e Abordagem Embora eu já venha compartilhando conteúdo técnico em eventos e plataformas como o GitHub, eu queria um espaço para trazer artigos mais detalhados e estruturados em torno de histórias reais. O objetivo é ir além de tutoriais simplistas e documentar de verdade o processo de construção e aprendizado, incluindo os tropeços e desafios que surgem no caminho. O site em si foi construído com [Hugo](https://gohugo.io/) e o tema [Blowfish](https://blowfish.page/). Essa escolha reflete minha preferência por ferramentas rápidas, simples e customizáveis, o que é um padrão recorrente nas decisões técnicas sobre as quais costumo escrever. ## O que esperar O conteúdo deste blog será centrado em jornadas técnicas pessoais. Acredito que até os conceitos mais complexos se tornam claros quando apresentados como uma narrativa. Você pode esperar artigos sobre: * **Explorações Técnicas:** Posts sobre Go, Python, IA Generativa e Engenharia de Dados no Google Cloud. * **Processos e Fluxos de Trabalho:** Reflexões sobre workflows de desenvolvimento, boas práticas e navegação na carreira. * **Uma Perspectiva com os Pés no Chão:** O conteúdo também trará referências ocasionais aos meus interesses além do núcleo tech, como gatos e café. Este blog é um convite aberto para uma conversa técnica. Estou super aberta a colaborações e convites para palestras. Se quiser trocar uma ideia ou se conectar, você me encontra no [LinkedIn](https://www.linkedin.com/in/petruzalek/) ou por e-mail em [daniela@danicat.dev](mailto:daniela@danicat.dev). Muito obrigada pela leitura! Seu feedback e sugestões para os próximos temas são sempre bem-vindos nos comentários abaixo. Dani =^.^= P.S.: Vale lembrar que as opiniões expressas neste blog são estritamente minhas e não representam necessariamente o posicionamento do meu empregador. ---