Um agente de IA respondendo perguntas já não impressiona ninguém. O negócio fica interessante quando você precisa de vários trabalhando juntos: um que classifica o pedido, outro que pesquisa, um terceiro que escreve, e uma pessoa que dá o aval antes de publicar qualquer coisa. Coordenar tudo isso é o que se chama de orquestração de agentes, e é exatamente o que vamos montar neste tutorial, do zero e passo a passo, usando Go e a versão 2.0 do kit do Google.
Se você nunca ouviu falar em orquestração, relaxa: começamos pelo conceito, com diagramas incluídos. Se já tem o modelo mental, pule direto para o tutorial.

O que é orquestrar agentes de IA (e por que importa)
Um agente de IA é, em essência, um LLM com instruções e ferramentas: entra texto, ele decide o que fazer e devolve um resultado. Os servidores MCP estão se consolidando como a forma padrão de conectar essas ferramentas. Um único agente funciona bem para tarefas delimitadas, mas fica curto quando o problema tem várias fases ou precisa de especialização.
Orquestrar agentes é definir como vários deles se coordenam para completar um trabalho: quem vai primeiro, quem recebe a saída de quem, quais decisões abrem um caminho ou outro, o que roda em paralelo e em quais pontos uma pessoa intervém. É a mesma ideia de uma orquestra: cada músico toca a sua parte, mas alguém precisa marcar o compasso.
Os quatro padrões básicos que cobrem 95% dos sistemas reais:
SEQUENCIAL ROTEAMENTO PARALELO HUMAN-IN-THE-LOOP
A ──▶ B ──▶ C A ──▶ tipo? ──┬─▶ B ┌─▶ B ─┐ A ──▶ [pessoa] ──▶ B
├─▶ C ├─▶ C ─┼─▶ E
└─▶ D └─▶ D ─┘
O problema é que, se você montar isso na mão em Go, acaba com goroutines e canais por todo lado, um switch gigante para decidir qual agente roda agora e, quando precisa que uma pessoa aprove algo no meio do caminho, um sistema caseiro de “pausa e retomada” com Redis e boa vontade. Funciona, mas é código de encanamento que não agrega nada ao seu produto.
É aí que entra a versão 2.0 do Agent Development Kit do Google para Go: ela traz um motor de workflows baseado em grafos como primitiva do próprio framework. Você modela o fluxo como um grafo de nós, declara as arestas, e o scheduler cuida do paralelismo, do roteamento e das pausas de human-in-the-loop. Vamos montar os quatro padrões do diagrama usando os exemplos oficiais de workflow.
Passo 1: preparar o ambiente
O módulo v2 tem seu próprio path de import, como manda a convenção do Go:
go get google.golang.org/adk/v2
A referência completa da API está em pkg.go.dev/google.golang.org/adk/v2. Workflows que usam apenas nós de função não precisam de mais nada. Os que chamam um LLM precisam de credenciais do Gemini: ou uma API key em GOOGLE_API_KEY, ou Vertex AI com Application Default Credentials (gcloud auth application-default login mais GOOGLE_GENAI_USE_VERTEXAI=true, GOOGLE_CLOUD_PROJECT e GOOGLE_CLOUD_LOCATION), conforme indica o README dos exemplos.
Para acompanhar o tutorial, clone o repositório com os exemplos:
git clone https://github.com/google/adk-go.git
cd adk-go
Passo 2: seu primeiro workflow sequencial
O motor vive no pacote google.golang.org/adk/v2/workflow. Todo workflow é um grafo dirigido: os nós são unidades de trabalho e as arestas definem quem entrega dados para quem. Segundo a documentação dos exemplos, existem cinco tipos de nó:
FunctionNode: qualquer função Go, com entrada/saída tipadas. O cavalo de batalha.AgentNode: envolve umLlmAgentpara que ele participe como mais um nó.ToolNode: envolve umtool.Tool.JoinNode: barreira de fan-in; espera todos os seus predecessores e entrega ummap[nomeDoNó]output.DynamicNode: orquestrador imperativo para quando você quer decidir em Go, em tempo de execução, quais filhos rodar.
O exemplo mais simples é o basic: dois FunctionNode encadeados com workflow.Chain. O primeiro coloca o texto do usuário em maiúsculas e o segundo adiciona um sufixo:
nodeA := workflow.NewFunctionNode("upper", upperFn, nodeConfig)
nodeB := workflow.NewFunctionNode("suffix", suffixFn, nodeConfig)
edges := workflow.Chain(workflow.Start, nodeA, nodeB)
myWorkflow, err := workflowagent.New(workflowagent.Config{
Name: "simple_sequence_workflow",
Description: "Converts string to uppercase and appends a suffix",
Edges: edges,
})
O grafo é empacotado como um agente normal com workflowagent.New, então dá para servi-lo com o launcher de console que vem no próprio ADK sem escrever uma linha de servidor. Rode assim:
go run ./examples/workflow/basic/ console
Você tem um chat no terminal para testar o fluxo. Sem Docker, sem UI, sem nada — e sem API key: o basic não toca em nenhum LLM.
Passo 3: roteamento, ou como decidir o caminho com um LLM
O padrão sequencial quebra quando a fase seguinte depende do conteúdo: um e-mail furioso não se trata igual a uma dúvida técnica. É aí que entra o roteamento, e o exemplo routing/llm ensina a separação de responsabilidades correta: o LLM só classifica, e o motor faz o roteamento. Nada de “o modelo devolve JSON com o próximo passo e eu faço o parse e rezo”.
Um AgentNode com um LlmAgent classificador responde com exatamente uma palavra (question, exclamation ou statement). Depois, um nó emissor transforma essa palavra em rotas:
func routeByClassification(ctx agent.Context, input any, emit func(*session.Event) error) (any, error) {
category := strings.TrimRight(strings.ToLower(strings.TrimSpace(fmt.Sprint(input))), ".")
if category != "question" && category != "exclamation" && category != "statement" {
category = "statement" // fallback defensivo se o LLM sair do roteiro
}
ev := session.NewEvent(ctx, ctx.InvocationID())
ev.Routes = []string{category}
if err := emit(ev); err != nil {
return nil, err
}
return nil, nil
}
E as arestas declaram qual rota ativa cada destino:
edges := workflow.Concat(
workflow.Chain(workflow.Start, classifyNode, routeNode),
[]workflow.Edge{
{From: routeNode, To: question, Route: workflow.StringRoute("question")},
{From: routeNode, To: statement, Route: workflow.StringRoute("statement")},
{From: routeNode, To: exclamation, Route: workflow.StringRoute("exclamation")},
},
)
Existem StringRoute, IntRoute e MultiRoute para roteamento por valor. O detalhe-chave: se o LLM inventar uma categoria, sua função normaliza e cai no ramo padrão. O controle é seu, não do modelo. Uma observação do exemplo que vale copiar: registre os agentes LLM envolvidos no SubAgents da config do workflowagent, senão o runner solta um “Event from an unknown agent” a cada turno. Este exemplo sim precisa das credenciais do passo 1.
Passo 4: paralelismo com fan-out e fan-in
Quando várias tarefas não dependem entre si, rodá-las em série é jogar tempo fora. O exemplo complex monta uma pipeline de pesquisa: três agentes pesquisadores (energia renovável, veículos elétricos, captura de carbono) rodam em paralelo, um JoinNode espera os três, um FunctionNode formata os resultados e um agente de síntese escreve o relatório final. A fiação são quatro linhas:
eb := workflow.NewEdgeBuilder()
eb.AddFanOut(workflow.Start, renewableNode, electricVehicleNode, carbonNode)
eb.AddFanIn(gatherNode, renewableNode, electricVehicleNode, carbonNode)
eb.Add(gatherNode, formatNode)
eb.Add(formatNode, synthNode)
O JoinNode (workflow.NewJoinNode("gather")) dispara uma única vez, depois que todos os predecessores terminam, e entrega ao sucessor um map[string]any indexado pelo nome do nó. É por isso que o exemplo usa constantes para os nomes dos agentes: elas também são as chaves do mapa. Nada de sync.WaitGroup, canal de resultados ou select. Essa é exatamente a parte de encanamento que o motor tira das suas costas.
Isso é diferente de montar uma equipe de agentes autônomos com MiniMax Code, onde cada agente é um papel; aqui o workflow é um grafo dirigido com arestas explícitas.
Passo 5: human-in-the-loop, pausar para uma pessoa decidir
Último padrão, e o que mais me surpreendeu, porque costuma ser a primeira coisa que você acaba implementando na mão em qualquer sistema sério de agentes. No ADK 2.0, pausar um workflow é emitir um evento RequestInput e retornar ErrNodeInterrupted, como faz o exemplo hitl_simple:
ask := workflow.NewEmittingFunctionNode[any, any]("ask_name",
func(ctx agent.Context, _ any, emit func(*session.Event) error) (any, error) {
if err := emit(workflow.NewRequestInputEvent(ctx, session.RequestInput{
InterruptID: "ask_name-" + uuid.NewString(),
Message: "What's your name?",
})); err != nil {
return nil, err
}
return nil, workflow.ErrNodeInterrupted
},
workflow.NodeConfig{},
)
Teste direto:
go run ./examples/workflow/hitl_simple/ console
O launcher de console mostra o prompt, a pessoa responde, e essa resposta é entregue ao próximo nó como sua entrada tipada. Repare no InterruptID com um UUID novo por requisição: o comentário do próprio exemplo avisa que reutilizar IDs pode fazer a Dev UI tratar um prompt posterior como já respondido. Detalhe pequeno que economiza uma tarde de depuração.
Antes de deixar um LLM tomar decisões críticas, vale documentar o contexto do sistema: um arquivo AGENTS.MD reduz alucinações e define regras de comportamento.
Se o seu caso é “um único nó que pergunta e se reexecuta com a resposta” em vez de passar para outro nó, o exemplo hitl_rerun usa ResumeOrRequestInput para esse padrão de reentrada. E para aprovações no meio de uma pipeline (publicar isso sim ou não, rodar esse deploy sim ou não), o padrão é o mesmo: um nó emissor de RequestInput entre o nó que prepara a ação e o que a executa.
Vindo do ADK 1.0? Os breaking changes que vão te afetar
O README-v2 documenta os breaking changes. Os dois que vão te afetar com certeza:
session.NewEventagora exige umcontext.Contextcomo primeiro argumento:session.NewEvent(ctx, ctx.InvocationID()). O ID e o timestamp do evento vêm do pacoteplatform, o que permite aos motores de workflow produzir eventos determinísticos e reexecutáveis. Se você tinha um helper sem contexto, adicione o parâmetro e passe de cima para baixo; nada decontext.Background()no meio da cadeia.ToolContexteCallbackContextforam fundidos em um únicoagent.Context(PR #945). Seus mocks de teste vão quebrar se implementavam a interface antiga. A saída confortável: embutaagent.StrictContextMockno seu fake, sobrescreva só o que o teste usa e pare de remendar mocks toda vez que a interface cresce. Métodos não sobrescritos dão panic com “not implemented”, que é exatamente o que você quer num teste.
Do zero ao workflow orquestrado: recapitulando
Agora você tem o modelo mental completo: orquestrar é definir um grafo de agentes e passos, e os quatro padrões que você precisa são cadeia, roteamento, paralelo e pausa humana. Com o ADK Go 2.0 cada padrão são poucas linhas declarativas, e o scheduler engole o encanamento. Se o que você procura é um agente autônomo que trabalhe fora de um grafo fixo, a configuração óptima do OpenClaw é um bom complemento a este tutorial.
Se o seu sistema cabe numa cadeia linear, um Chain com três FunctionNode e pronto — você não precisa de mais nada. Onde o motor de grafos começa a compensar é quando aparece qualquer uma destas três coisas: decisões que dependem da saída de um LLM, trabalho que pode rodar em paralelo, ou uma pessoa que precisa aprovar algo antes de continuar. Nesse ponto, a alternativa é escrever o scheduler você mesmo, e acredite: você não quer dar manutenção num scheduler.
Tudo o que você viu roda local com um go run, e a maioria dos exemplos (7 dos 10 do repositório) nem precisa de API key. A ordem que eu seguiria para aprender: basic, depois hitl_simple, em seguida routing/llm e por fim complex. Numa tarde você sai de não saber o que é orquestração para ter um workflow multiagente funcionando na sua máquina.
Perguntas frequentes
O que é orquestração de agentes de IA?
É coordenar vários agentes de IA para completar um trabalho: definir a ordem de execução, quem recebe a saída de quem, quais decisões abrem caminhos diferentes, o que roda em paralelo e em quais pontos uma pessoa intervém. Costuma ser modelada como um grafo de nós (passos) e arestas (fluxo).
O que preciso para seguir este tutorial?
Go instalado e o repositório google/adk-go clonado. Os exemplos basic, hitl_simple, routing/string, routing/int, hitl_rerun e os de dynamic não precisam de API key. Para routing/llm e complex você precisa de credenciais do Gemini (GOOGLE_API_KEY) ou Vertex AI.
Como funciona o human-in-the-loop no ADK Go 2.0?
Um nó emissor envia um evento RequestInput e retorna workflow.ErrNodeInterrupted. O launcher mostra o prompt, a pessoa responde e a resposta tipada chega ao próximo nó como entrada. Assim é possível modelar aprovações ou perguntas interativas.



O que você achou?
Deixe sua opinião, pergunta ou sugestão. Os comentários são sincronizados com GitHub Discussions .