Agents on Call, Parte 5. O Time: Supervisor e Três Especialistas
Um supervisor delega a agentes de triagem, runbook e custo pela rede, o AgentCore Memory amarra as descobertas, e quando um agente só ainda vence.

Quatro agentes existem agora onde a Parte 4 deixou um: um supervisor, um agente de runbook e um agente de custo se juntam ao de triagem de incidentes, coordenados não por chamadas de função Python dentro de um processo, mas pela própria API InvokeAgentRuntime do AgentCore Runtime, porque os quatro continuam sendo implantados como recursos de Runtime separados e isolados, o mesmo isolamento pelo qual a Parte 3 escolheu o AgentCore Runtime em primeiro lugar. Mais um recurso AWS os amarra: uma única instância do AgentCore Memory, compartilhada entre os quatro por actor ID, de modo que um diagnóstico que a triagem escreve às 3 da manhã continua legível para o agente de runbook que o recebe segundos depois, e para qualquer agente que olhar de novo, no mês seguinte, um incidente com o mesmo formato deste.
A Parte 1 escolheu AgentCore mais Strands e esboçou quatro agentes atrás de um supervisor como a forma-alvo. A Parte 2 construiu a fronteira de conta e a divisão ops-readonly/ops-mutate que todo agente desde então reutiliza. A Parte 3 entregou o primeiro agente, duas tools conectadas diretamente em seu próprio processo. A Parte 4 moveu essas tools, mais uma segunda tool de leitura e a única tool mutante da plataforma, para trás do AgentCore Gateway: quatro Lambdas que um cliente MCP consegue descobrir, em vez de quatro coisas para conectar à mão por agente. Esta parte é onde esse investimento começa a render para mais de um chamador: os agentes de runbook e de custo se conectam ao mesmo Gateway e herdam as quatro tools sem re-derivar de nada o tratamento de credenciais entre contas. O código complementar vive em github.com/flightlesstux/agents-on-call, em agents/supervisor/, agents/runbook/, agents/cost_optimizer/ e nas adições desta parte a terraform/10-agent-runtime/; todo trecho abaixo é extraído diretamente desses arquivos, não simplificado para o post.
Quando um agente deixa de ser suficiente
O agente de triagem da Parte 3 tinha um system prompt, duas tools, um trabalho. Isso se sustentou bem através das quatro tools da Parte 4, porque as quatro ainda serviam a esse único trabalho: diagnosticar, nunca remediar. A pressão que de fato força um segundo agente não é a contagem de tools, é a contagem de trabalhos. Um agente de runbook (transformar um diagnóstico numa proposta de execução de SSM Automation) e um agente de custo (ler o Cost Explorer uma vez por dia, por conta própria, e propor rightsizing) não são variações do trabalho da triagem, são trabalhos diferentes, com agendas diferentes e, se espremidos num único system prompt, instruções contraditórias vivendo na mesma janela de contexto. "Você nunca executa uma ação mutante, você não tem nenhuma tool capaz disso" (a regra da própria triagem, inalterada desde a Parte 3) e "proponha uma chamada de ssm_execute quando um chunk de runbook correspondente a sustentar" (o trabalho real do agente de runbook) não são frases que um único prompt enuncia de forma limpa juntas; um agente instruído a fazer ambos desloca a disciplina de um trabalho na direção da disposição do outro de agir, ou perde a nuance do segundo trabalho sob a cautela do primeiro. Dividir o trabalho entre processos, não só entre seções de prompt, é o que mantém a disciplina da própria triagem legível duas partes depois: seu system prompt não mudou uma linha desde a Parte 3.
O inchaço de contexto é a versão mais silenciosa da mesma pressão. Um agente de triagem que também carrega as manhas do Cost Explorer e o comportamento de retrieval do corpus de runbooks gasta tokens de system prompt e peso de schema de tools em trabalhos que não está fazendo em qualquer invocação dada, na maior parte do tempo. Quatro agentes, cada um carregando apenas o que o próprio trabalho precisa, é uma decisão de eficiência de tokens tanto quanto organizacional.
Agentes como tools, a forma que cabe em quatro Runtimes separados
O Strands Agents 1.0 entregou quatro novas primitivas multiagente mais suporte ao protocolo Agent-to-Agent em sua versão de produção, e "agentes como tools" tem mais de uma forma real no SDK. Passar uma instância filha de Agent diretamente na lista tools=[...] de um pai, ou envolver uma num decorator que a expõe com um tool spec, ambos funcionam bem e ambos exigem a mesma coisa: o subagente vive no mesmo processo Python que seu chamador. Essa não é a forma desta plataforma. Triagem, runbook e custo são implantados cada um como seu próprio recurso aws_bedrockagentcore_agent_runtime, versionados de forma independente, com escopo IAM independente, cada um no isolamento de sessão pelo qual a Parte 3 escolheu o AgentCore Runtime especificamente. Colapsar três Runtimes num único processo para usar o atalho em processo desfaria esse isolamento por uma conveniência sintática.
| Forma | Como funciona | Cabe nesta plataforma? |
|---|---|---|
tools=[agent_instance] | Passa um objeto Agent do Strands diretamente na lista de tools do próprio pai | Não: exige o subagente no mesmo processo, colapsando em um o isolamento de três Runtimes separados |
.as_tool() | Envolve uma chamada de agente em processo num decorator que expõe um tool spec | Não, mesmo motivo: ainda um processo, um raio de impacto se ele cair |
@tool chamando InvokeAgentRuntime | Uma função de tool simples do Strands que chama pela rede o Runtime já implantado de outro agente | Sim: casa com a forma de quatro Runtimes separados com que o Terraform da Parte 3 já se comprometeu |
Então o supervisor usa a terceira forma: funções decoradas com @tool que chamam a operação de data plane InvokeAgentRuntime do bedrock-agentcore contra o ARN de runtime de cada especialista. O trade-off é real e vale nomear em vez de esconder: uma chamada em processo é uma chamada de função, isto é um salto de rede com sua própria latência e seu próprio jeito de falhar no meio do caminho. Pago deliberadamente, não por acidente, pelo isolamento com que a Parte 3 já se comprometeu:
def _invoke_specialist(agent_runtime_arn: str, actor_id: str, prompt: str) -> str:
"""Invoke one specialist's AgentCore Runtime and return its text result.
...
"""
response = _agentcore.invoke_agent_runtime(
agentRuntimeArn=agent_runtime_arn,
runtimeSessionId=_session_id,
contentType="application/json",
payload=json.dumps({"prompt": prompt, "session_id": _session_id}).encode("utf-8"),
)
if response["statusCode"] != 200:
raise RuntimeError(f"{actor_id} runtime invocation failed with status {response['statusCode']}")
body = json.loads(response["response"].read())
if "error" in body:
raise RuntimeError(f"{actor_id} returned an error: {body['error']}")
return body["result"]
@tool
def ask_triage_agent(alarm_context: str) -> str:
"""Ask the incident-triage specialist to diagnose a firing alarm.
...
"""
result = _invoke_specialist(TRIAGE_AGENT_RUNTIME_ARN, "incident-triage", alarm_context)
record_incident_event("supervisor", _session_id, {"delegated_to": "incident-triage", "result": result})
return resultDois detalhes em que vale a pena parar. Primeiro, _invoke_specialist levanta exceção num status não-200 ou num body de erro em vez de retornar uma string que se lê como uma descoberta normal: um supervisor que não consegue distinguir "o runbook não encontrou nada" de "a invocação do runbook falhou" vai sintetizar falsa confiança no que quer que diga a um humano. Segundo, _session_id é cunhado uma vez por invocação do supervisor, não uma vez por chamada de especialista, e passado a todo especialista sem alteração: é o que permite ao AgentCore Runtime agrupar os traces dos três especialistas sob um único incidente, e o que permite à memória compartilhada abaixo distinguir os eventos de um incidente dos de outro.
O agente de runbook: Knowledge Base sobre documentos SSM, propõe, nunca executa
O trabalho inteiro do agente de runbook é transformar um diagnóstico numa remediação candidata, tirada de um documento real, não inventada. Ele chega lá com duas fontes de tools: uma nova tool kb_retrieve sobre uma Bedrock Knowledge Base construída sobre o corpus de runbooks, e toda tool que o Gateway já indexa, descoberta no momento da conexão em vez de declarada à mão:
@tool
def kb_retrieve(query: str, max_results: int = 5) -> str:
"""Search the runbook Knowledge Base for playbook chunks relevant to query.
...
"""
response = _bedrock_agent_runtime.retrieve(
knowledgeBaseId=RUNBOOK_KNOWLEDGE_BASE_ID,
retrievalQuery={"text": query},
retrievalConfiguration={"vectorSearchConfiguration": {"numberOfResults": max_results}},
)
...
return json.dumps({"query": query, "chunks": chunks})
_gateway = connect_gateway_tools()
agent = Agent(
model=model,
system_prompt=SYSTEM_PROMPT,
tools=[kb_retrieve, *_gateway.list_tools_sync()],
name="runbook",
...
)connect_gateway_tools() assina toda requisição ao endpoint MCP do Gateway com as credenciais da própria execution role do container de runtime, SigV4, casando com a escolha da Parte 4 de authorizer_type = "AWS_IAM" no próprio recurso do Gateway, e retorna as tools que aquela role IAM consegue alcançar: cloudwatch-read, logs-read, cost-read e ssm-execute, as mesmas quatro que a Parte 4 construiu, não listadas nem referenciadas em lugar nenhum do código deste agente. Se o modelo de fato chama ssm-execute é uma decisão de system prompt e de IAM, não de tempo de código. E o ssm-execute não mudou desde a Parte 4: ainda exige diagnosis e blast_radius em toda chamada, ainda só inicia o fluxo de aprovação do Step Functions, nunca uma ação direta numa conta spoke. O system prompt do agente de runbook faz disso uma regra dura em vez de implícita: chamar ssm_execute é reportado ao supervisor como "enviado a um humano", nunca como "feito", por mais confiante que o texto do próprio modelo soe.
O agente de custo: uma varredura agendada para o mesmo portão
O agente de custo é o único especialista que nenhum alarme acorda: o EventBridge Scheduler o invoca uma vez por dia por spoke, sem provocação de alarme algum, com a agenda em si definida inteiramente em Terraform em vez de deixada como alegação num docstring:
resource "aws_scheduler_schedule" "cost_agent_daily" {
name = "${var.platform_name}-cost-agent-daily"
description = "Daily trigger for the cost agent's Cost Explorer sweep across configured spoke accounts."
schedule_expression = "cron(0 6 * * ? *)"
flexible_time_window {
mode = "OFF"
}
target {
arn = "arn:aws:scheduler:::aws-sdk:bedrockagentcore:invokeAgentRuntime"
role_arn = aws_iam_role.cost_scheduler.arn
...
}
}Ele lê através da mesma conexão de Gateway que o runbook (cost-read, mais cloudwatch-read e logs-read para corroborar uma anomalia de gasto contra carga real antes de sinalizá-la), e quando uma ação de rightsizing parece justificada, propõe pelo caminho idêntico de ssm-execute que o runbook usa. Um pipeline de aprovação, dois proponentes: um humano revisando o Slack não deveria precisar saber nem se importar se uma proposta pendente veio de um diagnóstico de incidente ou de uma varredura noturna.
A única coisa que vale projetar deliberadamente é não relitigar uma negação. Uma varredura diária sem memória de ontem vai sinalizar a mesma instância "ociosa" toda manhã até que um humano a aprove ou canse de negá-la, e nenhum dos dois desfechos é bom:
prior_episodes = retrieve_incident_context("cost", prompt)
if prior_episodes:
prompt = (
f"{prompt}\n\nNote: {len(prior_episodes)} related past cost proposal(s) "
"exist in memory; check whether any were denied before re-proposing."
)AgentCore Memory: um incidente compartilhado, quatro escritores
O AgentCore Memory cobra eventos de curto prazo a $0,25 por 1.000 chamadas de CreateEvent e retrieval de longo prazo a $0,50 por 1.000 chamadas de RetrieveMemoryRecords, barato o bastante para que quatro agentes, cada um escrevendo um evento por invocação, custem um erro de arredondamento perto do preço de tokens de uma única chamada de modelo. A estratégia que faz o trabalho aqui é a EPISODIC, um dos tipos embutidos da AWS: ela captura cada sessão como um episódio estruturado (contexto, raciocínio, ações, desfechos) e roda seu próprio passo de reflexão sobre os episódios para extrair padrões mais amplos, sem esta plataforma precisar fazer a engenharia de prompt dessa extração por conta própria. O suporte do Terraform ao tipo EPISODIC em aws_bedrockagentcore_memory_strategy chegou em 29 de abril de 2026, quase dois meses antes da data desta própria parte:
resource "aws_bedrockagentcore_memory" "incident" {
name = "${replace(var.platform_name, "-", "_")}_incident_memory"
description = "Shared short-term event stream and long-term episodic memory for one incident's supervisor, triage, runbook, and cost agents."
event_expiry_duration = var.memory_event_expiry_days
# memory_execution_role_arn deliberately omitted: per the fact table, it
# is required only when a memory uses a CUSTOM strategy with a
# model-processing override block (SEMANTIC_OVERRIDE,
# USER_PREFERENCE_OVERRIDE, and so on). EPISODIC below is a built-in
# type, not CUSTOM, so AWS runs its own reflection step without this
# platform needing to grant or manage a role for it.
tags = merge(var.tags, { Component = "memory" })
}
...
resource "aws_bedrockagentcore_memory_strategy" "episodic" {
name = "incident-episodes"
memory_id = aws_bedrockagentcore_memory.incident.id
type = "EPISODIC"
namespaces = ["/incidents/{actorId}"]
}Todo agente escreve nessa única memória através das mesmas duas funções, que é o que impede quatro Runtimes implantados de forma independente de divergirem em como leem e escrevem estado compartilhado:
def record_incident_event(actor_id: str, session_id: str, event: dict) -> str | None:
"""Write one short-term event to the shared incident memory.
...
"""
if not MEMORY_ID:
return None
response = _agentcore.create_event(
memoryId=MEMORY_ID,
actorId=actor_id,
sessionId=session_id,
eventTimestamp=time.time(),
payload=[
{
"conversational": {
"content": {"text": json.dumps(event, default=str)},
"role": "ASSISTANT",
}
}
],
)
return response["event"]["eventId"]Duas coisas que ela deliberadamente não faz. Retorna None em vez de levantar exceção quando o memory ID não está definido, para que um agente sob teste local sem um recurso de memória provisionado degrade para "sem memória compartilhada" em vez de falhar um diagnóstico por causa de uma variável de ambiente ausente. E actor_id é uma string que um agente passa, não um valor que esta função valida contra qualquer coisa, de propósito: adicionar um quinto agente depois não exige nenhuma mudança de schema aqui, apenas um novo actor escrevendo no mesmo formato de namespace.
Honestidade: quando multiagente é exagero
Quatro AgentCore Runtimes mais um recurso de memória mais rede entre runtimes é infraestrutura de verdade para o que um time de ops menor poderia rodar como um agente com sete tools e um system prompt mais longo. O AgentCore Runtime cobra $0,0895 por vCPU-hora e $0,00945 por GB-hora com tempo ocioso gratuito, então quatro runtimes pequenos não são caros em termos absolutos, mas são quatro coisas para implantar, quatro roles IAM para auditar, e um salto de InvokeAgentRuntime de latência e superfície de falha que um processo único nunca tem. O gatilho honesto para dividir não é "esta plataforma cresceu", é o mesmo com que esta parte abriu: trabalhos distintos cujas instruções se contradizem num único system prompt, ou agendas distintas (um agente acionado por alarme e uma varredura diária espontânea) que não compartilham um loop único natural. Uma plataforma de ops com um trabalho, triagem e nada mais, não recebe nada dessa pressão e deveria continuar sendo um agente com boas tools: nenhuma chamada entre runtimes sobre a qual raciocinar, nenhum recurso de memória para impedir de envelhecer mal, nenhum módulo Terraform com for_each coordenando três roles que poderiam muito bem ser uma só. Dividir cedo, antes de existir um segundo trabalho real, compra um isolamento de que ninguém precisava ainda, ao custo de um salto de rede que ninguém queria ainda também.
Modos de falha a observar
Quatro que valem ser nomeados antes disto rodar contra um incidente real. A triagem ainda não escreve na memória compartilhada: seu código da Parte 3 antecede o recurso de memória desta parte, e nada nesta parte o adapta, o que significa que os episódios da estratégia de reflexão estão perdendo o único agente que roda primeiro em quase todo incidente, uma lacuna real, não um descuido a maquiar. Uma chamada entre runtimes falhando no meio do caminho é uma falha diferente de uma em processo: _invoke_specialist levantando exceção no meio de um incidente deixa o que quer que o supervisor já tenha registrado na memória parado ali como uma linha do tempo parcial, útil para um humano assumindo o incidente à mão, mas fácil de ler erroneamente como completa se ninguém checar. O passo de reflexão da EPISODIC roda de forma assíncrona sobre sessões encerradas, então retrieve_incident_context retornar uma lista vazia significa ou "genuinamente novo" ou "a reflexão ainda não rodou", e nem este código nem o prompt do agente conseguem hoje distinguir os dois. E as policies IAM dos novos agentes têm uma dependência de ordem real: a policy da role do supervisor referencia os próprios ARNs dos runtimes de runbook e de custo, então um apply do Terraform interrompido entre provisionar esses runtimes e provisionar a role do supervisor deixa o próximo plan mostrando a policy do supervisor como ainda-não-aplicada, não uma má configuração duradoura, mas um motivo para deixar um apply terminar em vez de confiar num parcial.
Leia isso a seguir
- Parte 4, Tools e o Gateway: MCP, Allowlists, Somente Leitura por Padrão, para as quatro tools às quais os agentes de runbook e de custo desta parte se conectam e que reutilizam sem re-derivar nada.
- Parte 6, Guardrails: A Parte que Todo Mundo Pula, onde cada invocação de modelo destes quatro agentes ganha uma camada de conteúdo e de prompt injection que nenhum deles tem ainda.
- How to call multiple terraform modules in a single terragrunt file no ercan.cloud, o mesmo problema de "coordenar várias unidades implantadas de forma independente sem duplicar a fiação entre elas" que os agent runtimes baseados em
for_eachdesta parte resolvem dentro de um único módulo, visto do lado multi-módulo.
Os diretórios completos agents/supervisor/, agents/runbook/, agents/cost_optimizer/ e o Terraform de memória e runtime por trás deles vivem no repositório complementar em github.com/flightlesstux/agents-on-call. Para o lado de banco de dados e infraestrutura de rodar plataformas como esta em escala, as notas de campo estão em ercan.cloud, e o hub fica em ercanermis.com.
Mais de Ercan
Mais dois sites, mesmo autor, terreno diferente.
Cloud, AWS, EKS, Terraform, engenharia de plataforma.
Notas de campo de sistemas em produção. EKS, IAM, Terraform em escala organizacional, observabilidade, otimização de custos.
Visitar ercan.cloud →O hub. Sobre, consultoria, contato.
Hub pessoal para as duas trilhas de escrita. Quem sou eu, como funciona a consultoria, como me contatar.
Visitar ercanermis.com →