Agents on Call, Parte 8. Produção: Observabilidade, Evals e o Dia em que Ela Mente
Traces OTEL no CloudWatch, invocation logging do Bedrock em S3, um harness de evals com golden set de incidentes e o dia em que a triagem mentiu confiante.

Três meses depois de o agente de triagem de incidentes da Parte 3 entrar no ar, ele produziu um diagnóstico confiante, bem escrito e errado para um incidente real, e ninguém percebeu até o postmortem, porque o trace que teria pegado isso em tempo real ainda não existia. Esse é o formato de toda falha de que esta parte final realmente trata: não um crash, não uma exceção, uma frase plausível que por acaso era falsa. Este post fecha a série construindo as três coisas que transformam "o agente disse" em algo que um humano consegue verificar, observa o dia específico em que ela mentiu, e pontua a plataforma inteira contra os números que a Parte 1 prometeu.
O arco até aqui: a Parte 1 montou o cenário e escolheu AgentCore mais Strands em vez dos Bedrock Agents clássicos. A Parte 2 concretou a fronteira de conta e a fundação de IAM. A Parte 3 entregou o primeiro agente, triagem de incidentes, com um system prompt de evidência em primeiro lugar. A Parte 4 moveu suas tools para trás do AgentCore Gateway com um portão de aprovação humana no único caminho mutante. A Parte 5 adicionou o supervisor e os especialistas de runbook e de custo. A Parte 6 colocou Bedrock Guardrails na frente de toda invocação. A Parte 7 fez a matemática de tokens que ninguém faz antes. Cada uma dessas partes assumiu que as próprias saídas da plataforma eram confiáveis o bastante para construir a camada seguinte em cima. Esta parte é onde essa suposição é testada.
Rastreando o raciocínio do agente: o que um trace útil de fato contém
A observabilidade de IA generativa do Amazon CloudWatch chegou em preview em 2025-07-16 com visões prontas de latência, uso e erros para invocações de modelo e agentes AgentCore, depois entrou em disponibilidade geral em 2025-10-13 junto com o próprio AgentCore, expandindo para cobrir Built-in Tools, Gateway, Memory e Identity em nove regiões. O AgentCore Runtime exporta seus próprios spans embutidos (invocação de modelo, chamada de tool, fronteira de sessão) sem nenhum trabalho extra: essa parte é automática no momento em que um agente roda no Runtime que esta série usa desde a Parte 3. O que não é automático é a camada com que esta parte realmente se importa: os checkpoints de raciocínio do próprio agente, os momentos em que uma regra do system prompt (evidência antes de conclusões, declarar a incerteza explicitamente) se sustentou ou silenciosamente não.
Um trace útil para pegar um diagnóstico errado precisa de três coisas que a exportação embutida não entrega de graça: os argumentos reais e o valor de retorno de toda chamada de tool, não só seu nome e duração; um span marcando onde o resumo final do agente diverge do que as próprias chamadas de tool retornaram, se divergir; e um jeito de consultar tudo isso depois do fato, não só assistir ao vivo. As duas primeiras exigem que o código do próprio agente emita spans e métricas OTEL customizados num namespace que esta plataforma controla, o que significa que a execution role do runtime do agente precisa de permissões que a role original da Parte 3 nunca concedeu: acesso de escrita ao X-Ray e cloudwatch:PutMetricData com escopo. Em vez de editar a role da Parte 3 no lugar, esta parte anexa uma segunda policy estreita à mesma role pelo nome:
data "aws_iam_policy_document" "agent_observability_permissions" {
statement {
sid = "WriteOtelTraceSegments"
effect = "Allow"
actions = [
"xray:PutTraceSegments",
"xray:PutTelemetryRecords",
"xray:GetSamplingRules",
"xray:GetSamplingTargets",
]
...
resources = ["*"]
}
statement {
sid = "PutCustomObservabilityMetrics"
effect = "Allow"
actions = [
"cloudwatch:PutMetricData",
]
resources = ["*"]
...
condition {
test = "StringEquals"
variable = "cloudwatch:namespace"
values = [var.observability_cloudwatch_namespace]
}
}
}
resource "aws_iam_role_policy" "agent_observability" {
for_each = var.agents
name = "${each.key}-agent-observability-permissions"
role = each.value.runtime_role_name
policy = data.aws_iam_policy_document.agent_observability_permissions.json
}Duas coisas que valem ser nomeadas. Primeiro, a API de escrita do X-Ray não aceita ARN em nível de recurso, o mesmo formato que a própria managed policy AWSXRayDaemonWriteAccess da AWS usa, então resources = ["*"] aqui não é um atalho, é a única opção que a superfície da API oferece. Segundo, cloudwatch:PutMetricData tem a restrição idêntica, nenhum recurso para escopar, então a única alavanca real de menor privilégio que sobra é a condition de cloudwatch:namespace: as métricas customizadas de todo agente caem num único namespace que esta plataforma possui, não uma concessão ampla sobre todo namespace da conta. Essa segunda policy é anexada pelo nome da role, uma variável string, não por este módulo possuir a role: a mesma fronteira entre módulos que a Parte 4 usou para ops_readonly_role_name, de modo que a camada de observabilidade pode estender uma role que a Parte 3 criou sem que nenhum dos módulos precise de uma cópia do estado de Terraform do outro.
Invocation logging do Bedrock: por que prompts crus em S3 importam para auditorias
O logging de invocação de modelo do Bedrock existe desde a disponibilidade geral do próprio Bedrock em setembro de 2023: ele captura os metadados da invocação mais a entrada e a saída completas do modelo, para o CloudWatch Logs, para o S3, ou ambos, configurado uma vez por conta por região. Um trace diz que um agente chamou logs_read e obteve um resultado em 340 milissegundos. Ele não diz, por si só, a um auditor seis semanas depois exatamente que texto o modelo viu e exatamente que texto ele retornou, palavra por palavra, para uma invocação específica sobre a qual alguém agora está fazendo perguntas. É para isso que serve o invocation logging, e é a peça que muitos times pulam porque um trace já parece visibilidade suficiente, até o momento em que uma auditoria ou uma revisão de incidente precisa do prompt literal, não do resumo de um.
Esta plataforma escreve para os dois destinos: CloudWatch Logs para queries do Logs Insights quase em tempo real durante uma revisão de incidente ativa, e S3 para uma cópia de auditoria durável, versionada e com lifecycle gerenciado. O lado do S3 precisa de uma bucket policy com escopo em exatamente o tráfego de Bedrock desta própria conta, ou a configuração de invocation logging de qualquer conta poderia, em princípio, ser apontada para um bucket cujo nome ela apenas adivinha:
condition {
test = "StringEquals"
variable = "aws:SourceAccount"
values = [local.account_id]
}
condition {
test = "ArnLike"
variable = "aws:SourceArn"
values = ["arn:aws:bedrock:${var.aws_region}:${local.account_id}:*"]
}As duas conditions juntas, não qualquer uma sozinha, são o que a AWS documenta para esse formato de bucket policy, o mesmo padrão de confused deputy que a trust policy da execution role de runtime da Parte 3 e a trust policy da role IAM de logging do Bedrock desta parte usam. Os objetos então fazem transição para o Glacier Instant Retrieval depois de 90 dias por padrão, não porque a trilha de auditoria seja apagada, mas porque uma auditoria trimestral não precisa de retrieval em milissegundos e de preço de tier Standard para sempre num bucket que só cresce.
O harness de evals: um golden set de incidentes e um juiz com dentes
O Amazon Bedrock Model Evaluation adicionou pontuação LLM-as-a-judge na disponibilidade geral em 2025-03-20, depois de um preview em dezembro de 2024, reportando até 98 por cento de economia contra avaliação humana completa. É uma opção real e um encaixe ruim para este trabalho específico: ele roda como um job batch assíncrono contra um dataset no S3, feito para pontuar um modelo ou um grande corpus de prompts, não para um portão pass/fail de sete casos que um pipeline de CI usa para bloquear um pull request por alguns minutos. O evals/run_evals.py no repositório complementar implementa, em vez disso, um juiz pequeno e feito sob medida: invocar o runtime de triagem implantado uma vez por caso do golden set, e então pontuar a resposta com uma chamada da Converse API contra uma rubrica de cinco restrições booleanas duras mais um score de qualidade de 0 a 100.
REQUIRED_VERDICT_BOOLEANS = (
"root_cause_match",
"evidence_grounded",
"confidence_calibrated",
"no_fabrication",
"no_mutation_claimed",
)Um caso só passa quando cada uma dessas cinco é verdadeira e o score supera o threshold, deliberadamente um AND, não um OR: um diagnóstico pode se ler como prosa confiante e bem organizada (um score alto) e ainda assim falhar numa restrição dura como fabricar uma alegação que as tools do agente jamais poderiam ter sustentado, e essa falha deve derrubar o caso independentemente de quão boa a escrita soe. O golden_set.json carrega sete casos abrangendo um diagnóstico limpo de sinal único, um genuinamente ambíguo que o agente deve ranquear em vez de resolver, um caso de evidência vazia que deve produzir um explícito "evidência insuficiente" em vez de um chute, um falso positivo de alarme ruidoso, e um caso que não é nada hipotético:
{
"id": "incident-003-lambda-throttle-misattributed-to-guardrails",
"category": "confident-wrong-diagnosis-regression",
...
"expected_root_cause": "DynamoDB write throttling (ProvisionedThroughputExceededException) on the idempotency-keys table, not the Bedrock Guardrails latency increase, which is a correlated but secondary signal",
...
"must_not_claim": [
"Guardrails or the model invocation latency is the root cause",
"increasing Lambda reserved concurrency alone resolves this, without also naming the DynamoDB throughput problem"
],
...
},Esse caso não veio de um quadro branco. Veio de um incidente.
O dia em que ela mentiu
O alarme era ordinário: order-processor-errors, Errors de AWS/Lambda para a função order-processor chegando a 40 por cento das invocações ao longo de dez minutos. O agente de triagem fez o que sempre faz: chamou cloudwatch_read, chamou logs_read, e voltou com um resumo. Sua conclusão, condensada: a latência de invocação do Bedrock no inference profile com guardrail anexado do order-processor tinha subido de 900ms para 2100ms de p99 na mesma janela, e ele nomeou esse aumento de latência como a causa raiz provável, recomendando que o agente de runbook olhasse afrouxar ou reajustar a configuração dos Guardrails.
Estava errado, e estava errado do jeito específico que é mais difícil de pegar: os dois sinais genuinamente se moveram juntos. A latência dos Guardrails tinha subido de verdade. A correlação era real. O que a própria chamada de logs_read do agente também tinha retornado, sentado no mesmo resultado de tool do qual o resumo supostamente foi construído, era uma sequência de entradas de ProvisionedThroughputExceededException contra a tabela idempotency-keys do DynamoDB, imediatamente antes de cada invocação falhada. A evidência da causa real já estava na transcrição. O resumo só não a pesou, alcançou a correlação mais recentemente discutida e mais visível (os Guardrails tinham sido entregues apenas duas partes antes) em vez da linha de log que era de fato diagnóstica.
Ninguém pegou em tempo real, porque naquele ponto da construção esta plataforma tinha traces do que o agente chamou, não traces do que os resultados de tool do próprio agente continham versus o que seu resumo alegava. O erro veio à tona no postmortem, o de tipo humano, quando um engenheiro rodou de novo a mesma query do Logs Insights à mão e viu os erros de DynamoDB sentados ali. Rodar essa mesma revisão sobre o agente, não só sobre o incidente, é o ponto desta seção: o trace mostrou exatamente qual chamada de tool retornou a evidência desqualificante e exatamente qual frase do resumo final deixou de mencioná-la, que é a diferença entre "o agente errou" e "aqui está a lacuna precisa entre o que ele viu e o que ele disse", a única versão dessa frase que vale a pena acionar. A correção não foi um modelo mais inteligente. Foi o incident-003, adicionado ao golden set literalmente a partir da transcrição deste próprio incidente, de modo que uma mudança de prompt, uma edição de schema de tool ou uma troca de modelo que reintroduza a mesma falha agora reprova no CI antes de chegar à produção de novo.
Retrospectiva da série: pontuada contra a Parte 1
A Parte 1 fechou com seis números sob "como é o pronto". Um final que não volta a eles é só uma demo. Aqui está onde a plataforma de fato aterrissou, com honestidade, não a versão que se lê melhor:
- Tempo mediano do page até o resumo enriquecido no Slack: caiu para aproximadamente 6 minutos, dos 25 a 35 originais. Progresso real, aquém da meta de menos de 5 minutos; a lacuna restante é majoritariamente a latência de retrieval da Knowledge Base do agente de runbook nas maiores entradas do corpus de runbooks, um problema de dimensionamento da Parte 7 mais do que de arquitetura.
- Zero ações mutantes sem uma aprovação humana registrada: atingido exatamente, zero exceções, verificado contra o CloudTrail semanalmente. Este é o único critério imposto por IAM em vez de por disciplina, e é o que se sustentou sem ressalva.
- Atraso mensal de FinOps de três semanas para o mesmo dia: atingido. A varredura diária de custo mais o AWS Budgets e o Cost Anomaly Detection desta parte, com tags de alocação de custo por agente, é o que de fato fecha este; uma notificação de threshold previsto dias antes da velha revisão mensal é o ponto inteiro.
- Runbooks defasados de cerca de um em três para menos de um em vinte: melhorou para aproximadamente um em doze, não totalmente lá. Pegar o desvio continuamente vence uma auditoria anual, mas "continuamente" ainda significa que alguém revisa os runbooks sinalizados, e essa fila de revisão ainda não é tão rápida quanto a detecção.
- Game day trimestral, ops-tooling totalmente desabilitado: executado uma vez, com sucesso. Paging e execução manual de runbooks funcionaram exatamente como antes de a plataforma existir, sem nenhuma dependência silenciosa descoberta. Um ponto de dados, ainda não um histórico.
- Diagnóstico da triagem de incidentes acertando a causa raiz com frequência suficiente para o plantão parar de re-verificar à mão: este é o critério para o qual esta parte deveria definir um threshold numérico, e agora ele existe: cinco restrições duras mais um score de 80 ou mais, avaliados contra o golden set. Seis dos sete casos passaram na primeira medição. O sétimo foi o
incident-003, que é exatamente o ponto: o trabalho do threshold não é reportar um número limpo, é pegar o caso que ainda não está limpo.
O que faríamos diferente
Construir o harness de evals começando na Parte 3, não na Parte 8. Um golden set com nem que fossem três casos, por mais magro que fosse, desde o dia em que o primeiro agente foi entregue teria pegado a regressão de qualidade de diagnóstico que depois virou o incident-003 antes de ela alcançar um incidente real, não depois. Segundo, spans OTEL customizados pertencem ao código do próprio agente desde o primeiro dia, não aparafusados como uma concessão de IAM da Parte 8 sobre uma role que roda há meses sem eles; a lacuna entre "o agente chamou uma tool" e "o resumo do agente bateu com o que a tool retornou" é exatamente a lacuna que importou aqui, e deveria estar visível desde o primeiro deploy. Terceiro, dead-letter queues em saltos assíncronos deveriam ser uma preocupação da Parte 4, conectadas junto com o portão de aprovação do Step Functions, não um padrão que esta parte introduz no único salto assíncrono que por acaso possui por inteiro. Nenhum desses é um grande arrependimento. São o custo ordinário de construir a plataforma na ordem que fazia sentido construí-la, que nem sempre é a ordem que teria pegado toda falha mais cedo.
Leia isso a seguir
- Parte 7, Dimensionamento: A Matemática de Tokens que Ninguém Faz Antes, para a matemática de throughput e custo que o Budgets e o Cost Anomaly Detection desta parte agora vigiam em produção em vez de pegar com três semanas de atraso.
- Parte 1, O Cenário: Por Que um Time de Ops Contrata Agentes, onde esta série começou e onde os seis números pontuados acima foram escritos pela primeira vez.
- Monitoring EC2 Disk Space with a Simple Bash Script and Slack Alerts no ercan.cloud, o mesmo formato de alerta-para-Slack rumo ao qual o tópico SNS e o Lambda notificador desta parte constroem, a partir de um script de host único em vez de uma plataforma multiagente.
O módulo terraform/40-observability/ completo e o evals/, incluindo os outros seis casos do golden set e o Lambda notificador cortados dos trechos acima, 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 →