Aprendendo mais rápido com Antigravity
Criando frontends Flutter para ADK
Como posso criar um frontend Flutter para um agente quando esse agente é construído com uma SDK e uma linguagem que nunca usei antes?
Este foi o desafio que enfrentei ao abordar um agente baseado em Python escrito com Agent Development Kit (ADK). Com experiência limitada em Python e sem exposição anterior ao framework ADK, criar um cliente que se integre com o servidor backend apresentou uma curva de aprendizado significativa. Além disso, mesmo que eu pudesse obter um agente de codificação para gerar algo funcional, terminar o projeto sem entender o código também seria uma forma de falha.
Após alguns começos falsos, no entanto, encontrei uma resposta. Usando um fluxo de trabalho estruturado e iterativo com meu parceiro de codificação AI, Antigravity, criei uma habilidade do desenvolvedor reutilizável que codificou o que aprendi em cada rodada. Comecei do zero, gerei notas sobre o código, criei múltiplos aplicativos conectados ao deep_search (um coordenador de pesquisa multi-agente do repositório oficial ADK samples), e incrementalmente construí a habilidade e meu próprio entendimento. Em alguns momentos, eu estava usando múltiplos agentes ao mesmo tempo, um agente "autor" para criar a habilidade comigo e um agente "codificador" para usar essa orientação para construir frontends.
O que eu terminei foi uma habilidade de agente chamada flutter_frontend_for_adk. Ela inclui cinco documentos de referência que guiam a Antigravity através uma sequência de fases, cada uma terminando em um entregável. As primeiras fases geraram os seguintes arquivos de notas, para que eu pudesse estruturar como o agente "codificador" pensava sobre a tarefa enquanto analisava o agente e se preparava para gerar o aplicativo:
AGENT_INTERFACE_NOTES.md— Notas tomadas durante a análise do código-fonte do agente. O que ele está destinado a realizar e como é construído? Quais são as interfaces e APIs expostas por esse agente e como funcionam?FRONTEND_USAGE_NOTES.md— O primeiro esquema. O que a interface de usuário deve fazer e como os usuários devem interagir com ela?FRONTEND_ARCHITECTURE_NOTES.md— Um plano de arquitetura. Quais serviços, classes, estado e modelos devem ser criados?FRONTEND_DESIGN_NOTES.md— Um documento de design para a interface do usuário. Como será? Quais são as cores, fontes e outros detalhes?
Depois disso, o Antigravity poderia gerar o código da aplicação, executá-lo e testá-lo. Enquanto a habilidade (e eu) ainda estão em andamento, agora tenho um bom controle sobre ADK, bem como uma maneira útil de criar interfaces do usuário para agentes existentes.
Este é como aconteceu.
Comece com o trabalho dos outros
Não sou a única pessoa escrevendo habilidades por aqui, então o primeiro passo foi instalar algumas habilidades das equipes do Google Cloud e Flutter:
npx skills add google/agents-cli --skill google-agents-cli-adk-code
npx skills add flutter/skillsIsso me deu algumas noções básicas sobre ADK, sua ferramenta CLI e Flutter. Também tinha o servidor Dart MCP instalado, graças à extensão Dart para Antigravity.
Estabeleça o loop
Em vez de começar com um pedido para que o agente lance uma aplicação, estabeleci um tipo de “loop de aprendizagem” com ele. Primeiro, pedi ao agente programador para executar a workflow da minha habilidade e então trabalhei com o agente autor para avaliar manualmente a saída do programador, identificar lacunas nas instruções da habilidade e atualizar as diretrizes para melhorar os próximos ciclos. Após cada iteração, excluí as notas geradas e a aplicação, reiniciei tudo e descobri o próximo item que precisava aprender.
O loop consiste em cinco etapas distintas impulsionadas por conversas com o Antigravity:
1) Execute a atual habilidade: Em uma nova conversa, o agente de codificação executa o fluxo de trabalho definido pela versão atual do arquivo da habilidade.
Meu prompt: Este repositório inclui um agente construído com ADK, mas sem frontend ou cliente. Gostaria que você lesse as instruções em flutter-frontend-for-adk/SKILL.md e seguisse o fluxo de trabalho. Não examine a história do git, não se refira ao contexto das outras conversas que tivemos e não mude para outros branches do git.
2) Avalie a saída e as especificações: Em uma segunda conversa, o agente autor e eu inspecionamos os artefatos gerados, como notas arquiteturais, especificações de design e a estrutura do código resultante. Eu fui melhor em notar problemas estruturais, enquanto Antigravity ajudou a capturar todos os pequenos detalhes.
Meu prompt: Enquanto reviso as notas e o código, dê uma olhada nos artefatos gerados e me diga como você acha que eles correspondem às instruções e aos nossos objetivos.
3) Identifique lacunas e modos de falha: Identificamos áreas onde o agente codificador faltou orientação específica, fez suposições incorretas ou enfrentou problemas de integração.
Meu prompt: Notei dois problemas na última execução: primeiro, o gitignore raiz está ignorando a nova pasta frontend/lib/ devido a uma regra global ‘lib/’. Segundo, o agente codificador está executando todas as seis fases continuamente sem parar. Precisamos de um jeito de pausar e verificar os entregáveis após cada etapa.
4) Atualize a habilidade central e referências: Atualizamos as instruções em SKILL.md ou os sub-guia na pasta references/ para abordar as lacunas.
Meu prompt: Vamos atualizar a Fase 6 em SKILL.md para lidar explicitamente com o problema do gitignore adicionando ‘!frontend/lib’ se ‘lib/’ for ignorado globalmente. Também adicione uma etapa obrigatória de ‘Revisão’ no final das fases 1 a 4 instruindo o agente a pausar e esperar minha aprovação antes de prosseguir.
5) Limpe arquivos locais e execute novamente: Excluo os arquivos de especificação temporários, reseta a árvore de trabalho do git e volto ao início do loop.
Embora trabalhar com dois agentes tenha acelerado o processo, isso não tornou a coisa instantânea. Posso absorver apenas tanta informação de cada vez! Dentro de cada iteração, no entanto, eu lia as notas e os arquivos-fonte, revisava a conversa com Antigravity e (quando comecei a gerar código real) executava o aplicativo para ver como ele se comportava.
No início, eu estava lendo e aprendendo principalmente. No final, porém, eu sabia o suficiente sobre o que estava acontecendo para focar em coisas que estavam falhando consistentemente, começar a fazer minha própria pesquisa e ajustar o código para corrigir problemas. Depois de modificar as notas e o repositório de código, perguntava ao Antigravity como poderia atualizar a habilidade para que a geração futura do código parecesse mais com o que eu havia criado. Como resultado, a habilidade não era apenas instruções para o agente codificador, mas um registro do que estava aprendendo.
Loop no loop
Levou treze iterações para que eu chegasse a algo com o qual não me envergonharia de compartilhar. Aqui estão as primeiras seis, que foram principalmente sobre descoberta e anotação:
- Autonomia e mapeamento de espaço de trabalho — Percebi que um único arquivo
SKILL.mdera muito difícil para o agente navegar. Refatorei a habilidade para ser modular, criando uma pasta dedicada referências/ e escrevi o primeiro guia especializado:referencias/agent_discovery.md. Isso permitiu que o Antigravity analisasse a fonte do agente e criasseAGENT_INTERFACE_NOTES.mdpara registrar o que aprendeu. - Definindo comportamento e experiência do usuário — O próximo passo foi o uso da especificação para a frente: O que faria? Como um usuário interagiria com ele? Adicionei uma nova fase à habilidade, instruindo-o a ler referências/frontend_usage.md e entrevistar-me sobre preferências de plataforma e requisitos de recursos. Isso me proporcionou
FRONTEND_USAGE_NOTES.md, que definiu o propósito e o conjunto de recursos do aplicativo antes mesmo de tocar no código. - Estabelecendo o esboço estrutural — Arquitetura. Adicionamos
referencias/frontend_architecture.mdà habilidade, que focava em detalhes arquiteturais que a frente precisaria e eu fiz o agente produzirFRONTEND_ARCHITECTURE_NOTES.md. - Desenhando a estética visual — Criamos
referencias/frontend_design.mdpara instruir o agente sobre como definir estética visual e adicionamos instruções de esqueleto aSKILL.md. Este guia direcionou o agente a entrevistar-me sobre preferências de tema, quebra de ponto e animação, e o Antigravity começou a produzirFRONTEND_DESIGN_NOTES.md. - Gerando e executando um aplicativo — Neste ponto, eu era capaz de dizer ao Antigravity para começar a gerar o código flutter real com base em todas as especificações que havíamos coletado. O aplicativo falhou bastante tragicamente, e percebi que algum tipo de arquivo geral “melhores práticas” seria necessário para superar problemas prováveismente recorrentes. Adicionamos
referencias/frontend_best_practices.mdaos arquivos da habilidade para guiar a codificação real, e começamos a corrigir as coisas uma de cada vez.
A partir daqui, continuei iterando com o Antigravity conforme enfrentava um problema de cada vez, atualizando o código, ajustando a habilidade para corresponder e então apagando a frente gerada para tentar novamente. No processo, o loop ficou mais apertado e as mudanças menores. Realizei sete iterações adicionais:
- O aplicativo não pode fazer chamadas de rede! — Corrigiu as permissões e informações para macOS e iOS.
- Todo esse markdown não está sendo formatado! — Usou
flutter_markdownpara exibir corretamente o texto do agente, que frequentemente contém markdown. - Por que o código não está certo? — Adicionou lints, regras de formatação e outras verificações pós-geração.
- Classes seladas! — Tipos de mensagens diferentes devem compartilhar um tipo base para que o aplicativo possa usar declarações switch exaustivas.
- Por que as conversas não estão rolando? — Adicionou um
ScrollControllerpara avançar automaticamente listas de mensagens quando novas chegam. - Por que o aplicativo caiu quando eu rodei na web? — Removi
dart:ioe me baseei empackage:httppara networking. - Eventos parciais na minha lista! — ADK envia uma sequência de eventos, às vezes em pedaços. A interface do usuário precisava montar e apresentá-los de forma coerente ao invés de fornecer à usuária uma lista de todos os parciais (com formatação markdown quebrada).
- Por que a janela de ferramentas está em branco? — Como gerado, os aplicativos não lidavam corretamente com convenções de nomenclatura de ferramenta e o tratamento de eventos não identificava corretamente quando as ferramentas eram invocadas.
Um exemplo de “coisa que deu errado”
Para dar uma ideia do trabalho feito em cada um dos iterações, vamos caminhar por uma das mudanças: como a interface do usuário lida com eventos parciais.
O servidor ADK transmite eventos representando pequenos pedaços de conteúdo ou estágios parciais da execução de ferramentas. Se o aplicativo cliente renderizar cada evento de rede recebido como uma mensagem individual na lista de chat, no entanto, a interface se tornará fragmentada. Uma única resposta do agente apareceria como dezenas de blocos isolados e quebrados.
A forma antiga (uma lista ingênua):
// Renderizando cada pedaço de evento bruto em uma lista view
ListView.builder(
itemCount: rawEvents.length,
itemBuilder: (context, index) {
// Cada bloco parcial de texto é renderizado em um bolha separada
return ChatBubble(text: rawEvents[index].contentText);
},
)Este código leva a uma interface que parece com isso:
A solução para isso não é particularmente difícil, uma vez que você sabe o que fazer. Claro, eu não sabia, então comecei a perguntar coisas como “Como o ADK usa SSE para transmitir eventos?” e “Qual é a estrutura de dados em rede para um evento e como sei se é um evento parcial?”. Se houvesse algo chamado ‘aprendizado por vibração’, eu estava fazendo isso.
Descobri que você só precisa verificar uma bandeira, então ajustei o código na classe AgentProvider do frontend, pedi ao Antigravity para revisar caso eu tivesse esquecido algo óbvio e rodei o aplicativo para verificar. Foi preciso mais uma solicitação ao Antigravity para atualizar as melhores práticas de modo que cada novo frontend usasse essa abordagem.
A nova maneira (agregação no AgentProvider):
await for (final event in stream) {
if (event.partial && event.contentText != null) {
// Acumule o texto de streaming
_activeStreamingAuthor = event.author;
_activeStreamingResponse =
(_activeStreamingResponse ?? ‘’) + event.contentText!;
} else {
// Finalizado: Limpe o acumulador e adicione à lista de eventos permanentes
_activeStreamingResponse = null;
final updatedEvents = List<Event>.from(_activeSession!.events)
..add(event);
// Combine delta do estado
Map<String, dynamic> mergedState = _extractSessionStateMap(
_activeSession!,
);
if (event.actions.stateDelta.isNotEmpty) {
mergedState.addAll(event.actions.stateDelta);
}
// Crie uma nova cópia da sessão com eventos e estado atualizados
_activeSession = Session.fromJson({
'id': _activeSession!.id,
'app_name': _activeSession!.appName,
'user_id': _activeSession!.userId,
'events': updatedEvents.map((ev) => _serializeEvent(ev)).toList(),
'state': mergedState,
});
_processCompletedEvent(event);
}
notifyListeners();
}Tente você mesmo
Sou uma pessoa que aprende fazendo, e tentar algo totalmente fora da minha zona de conforto (como um novo idioma e uma nova SDK) foi bastante intimidante. Ter essa estrutura, no entanto, me ajudou muito, e agora tenho uma nova habilidade de agente.
Se você ainda não fez, baixe o Antigravity e dê uma tentada — levou apenas algumas horas para mim, e aprendi alguns truques novos.
Para começar:
- Baixe Antigravity para integrar o assistente de codificação agêntico ao seu ambiente de desenvolvimento.
- Visite a documentação AGY getting started para aprender como definir suas próprias habilidades de desenvolvedor personalizadas e referências de diretrizes.
- Esboce uma habilidade simples para um framework que você deseja explorar, e continue iterando até obter algo do seu gosto!

