Agents on Call, Teil 8. Produktion: Observability, Evals und der Tag, an dem die Plattform lügt
OTEL-Traces in CloudWatch, Bedrock Invocation Logging nach S3, ein Eval-Harness mit Golden Incident Set, und der Tag, an dem der Triage-Agent überzeugt log.

Drei Monate nachdem der Incident-Triage-Agent aus Teil 3 live gegangen war, produzierte er für einen echten Vorfall eine selbstbewusste, gut geschriebene, falsche Diagnose, und niemand fing sie bis zum Postmortem, weil der Trace, der sie in Echtzeit gefangen hätte, noch nicht existierte. Das ist die Form jedes Fehlers, um den es in diesem letzten Teil wirklich geht: kein Crash, keine Exception, ein plausibler Satz, der zufällig falsch war. Dieser Beitrag schließt die Serie, indem er die drei Dinge baut, die aus "der Agent hat es gesagt" etwas machen, das ein Mensch verifizieren kann, sich den konkreten Tag ansieht, an dem er log, und die ganze Plattform gegen die Zahlen bewertet, die Teil 1 versprochen hat.
Der Bogen bis hierher: Teil 1 hat das Szenario gesetzt und AgentCore plus Strands gegenüber den klassischen Bedrock Agents gewählt. Teil 2 hat die Kontogrenze und das IAM-Fundament gegossen. Teil 3 hat den ersten Agenten ausgeliefert, Incident Triage, mit einem Evidenz-zuerst-System-Prompt. Teil 4 hat seine Tools hinter AgentCore Gateway verschoben, mit einem Human-Approval-Gate auf dem einen mutierenden Pfad. Teil 5 hat den Supervisor sowie die Runbook- und Cost-Spezialisten hinzugefügt. Teil 6 hat Bedrock Guardrails vor jede Invocation gestellt. Teil 7 hat die Token-Rechnung gemacht, die niemand vorab macht. Jeder dieser Teile hat angenommen, dass den eigenen Outputs der Plattform genug vertraut werden kann, um die nächste Schicht darauf zu bauen. Dieser Teil ist der, in dem diese Annahme getestet wird.
Agent-Reasoning tracen: was ein nützlicher Trace tatsächlich enthält
Amazon CloudWatch generative AI observability erreichte am 16. Juli 2025 die Preview, mit Out-of-the-box-Ansichten für Latenz, Nutzung und Fehler von Modell-Invocations und AgentCore-Agenten, und wurde am 13. Oktober 2025 zusammen mit AgentCore selbst allgemein verfügbar, erweitert auf Built-in Tools, Gateway, Memory und Identity über neun Regionen. AgentCore Runtime exportiert seine eigenen eingebauten Spans (Modell-Invocation, Tool-Aufruf, Session-Grenze) ohne jeden Zusatzaufwand: Dieser Teil ist automatisch, sobald ein Agent auf der Runtime läuft, die diese Serie seit Teil 3 nutzt. Nicht automatisch ist die Schicht, um die es diesem Teil eigentlich geht: die eigenen Reasoning-Checkpoints eines Agenten, die Momente, in denen eine System-Prompt-Regel (Evidenz vor Schlussfolgerungen, Unsicherheit explizit benennen) entweder gehalten hat oder still nicht.
Ein Trace, der nützlich ist, um eine falsche Diagnose zu fangen, braucht drei Dinge, die der eingebaute Export nicht gratis liefert: die tatsächlichen Argumente und den Rückgabewert jedes Tool-Aufrufs, nicht nur seinen Namen und seine Dauer; einen Span, der markiert, wo die finale Zusammenfassung des Agenten von dem abweicht, was seine eigenen Tool-Aufrufe zurückgegeben haben, falls sie es tut; und einen Weg, all das im Nachhinein abzufragen, nicht nur live zuzusehen. Die ersten beiden brauchen den eigenen Code des Agenten, der Custom-OTEL-Spans und -Metriken in einen Namespace emittiert, den diese Plattform kontrolliert, was heißt, dass die Execution Role der Agent-Runtime Berechtigungen braucht, die Teil 3s ursprüngliche Rolle ihr nie gewährt hat: X-Ray-Schreibzugriff und gescoptes cloudwatch:PutMetricData. Statt Teil 3s Rolle in place zu editieren, hängt dieser Teil eine zweite, enge Policy per Namen an dieselbe Rolle:
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
}Zwei Dinge, die es zu benennen lohnt. Erstens nimmt X-Rays Write-API keine ARN auf Ressourcen-Ebene an, dieselbe Form, die AWS' eigene verwaltete AWSXRayDaemonWriteAccess-Policy nutzt, resources = ["*"] ist hier also keine Abkürzung, es ist die einzige Option, die die API-Oberfläche bietet. Zweitens hat cloudwatch:PutMetricData die identische Einschränkung, keine Ressource zum Scopen, der einzig verbliebene echte Least-Privilege-Hebel ist also die cloudwatch:namespace-Condition: Die Custom-Metriken jedes Agenten landen in einem Namespace, den diese Plattform besitzt, kein Pauschal-Grant über jeden Namespace im Konto. Diese zweite Policy hängt per Rollen-Namen an, einer String-Variable, nicht dadurch, dass dieses Modul die Rolle besitzt: dieselbe Cross-Modul-Grenze, die Teil 4 für ops_readonly_role_name genutzt hat, sodass die Observability-Schicht eine Rolle erweitern kann, die Teil 3 erstellt hat, ohne dass eines der beiden Module eine Kopie des Terraform-States des anderen braucht.
Bedrock Invocation Logging: warum rohe Prompts in S3 für Audits zählen
Bedrock Model Invocation Logging existiert seit Bedrocks eigener General Availability im September 2023: Es erfasst Invocation-Metadaten plus den vollständigen Modell-Input und -Output, nach CloudWatch Logs, nach S3 oder in beides, einmal pro Konto und Region konfiguriert. Ein Trace sagt einem, dass ein Agent logs_read aufgerufen und in 340 Millisekunden ein Ergebnis bekommen hat. Er sagt einem Auditor sechs Wochen später nicht von allein, exakt welchen Text das Modell gesehen und exakt welchen Text es zurückgegeben hat, Wort für Wort, für eine spezifische Invocation, zu der jetzt jemand Fragen stellt. Dafür ist Invocation Logging da, und es ist das Stück, das viele Teams überspringen, weil sich ein Trace bereits nach genug Sichtbarkeit anfühlt, genau bis ein Audit oder ein Incident-Review den wörtlichen Prompt braucht, nicht eine Zusammenfassung davon.
Diese Plattform schreibt in beide Ziele: CloudWatch Logs für Logs-Insights-Abfragen nahe Echtzeit während eines aktiven Incident-Reviews, und S3 für eine dauerhafte, versionierte, lifecycle-verwaltete Audit-Kopie. Die S3-Seite braucht eine Bucket-Policy, die exakt auf den eigenen Bedrock-Traffic dieses Kontos gescoped ist, sonst könnte die Invocation-Logging-Konfiguration irgendeines Kontos im Prinzip auf einen Bucket gerichtet werden, dessen Namen sie bloß errät:
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}:*"]
}Beide Conditions zusammen, nicht eine allein, sind das, was AWS für diese Bucket-Policy-Form dokumentiert, dasselbe Confused-Deputy-Muster, das Teil 3s Trust Policy der Runtime-Execution-Role und die Trust Policy der Bedrock-Logging-IAM-Rolle dieses Teils beide nutzen. Objekte wandern dann per Default nach 90 Tagen in Glacier Instant Retrieval, nicht weil der Audit-Trail gelöscht wird, sondern weil ein Quartals-Audit keine Millisekunden-Retrieval braucht und keine Standard-Tier-Preise für immer, für einen Bucket, der nur wächst.
Der Eval-Harness: ein Golden Incident Set und ein Judge mit Zähnen
Amazon Bedrock Model Evaluation hat LLM-as-a-Judge-Scoring am 20. März 2025 zur General Availability gebracht, nach einer Preview im Dezember 2024, mit berichteten Kosteneinsparungen von bis zu 98 Prozent gegenüber vollständiger menschlicher Evaluation. Es ist eine echte Option und ein schlechter Fit für genau diese Aufgabe: Es läuft als asynchroner Batch-Job gegen ein Dataset in S3, gebaut, um ein Modell oder einen großen Prompt-Korpus zu bewerten, nicht für ein Sieben-Fälle-Pass/Fail-Gate, mit dem eine CI-Pipeline einen Pull Request für ein paar Minuten blockiert. evals/run_evals.py im Begleit-Repo implementiert stattdessen einen kleinen, zweckgebauten Judge: die deployte Triage-Runtime einmal pro Golden-Set-Fall aufrufen, dann die Antwort mit einem Converse-API-Aufruf gegen eine Rubrik aus fünf harten booleschen Constraints plus einem Qualitäts-Score von 0 bis 100 bewerten.
REQUIRED_VERDICT_BOOLEANS = (
"root_cause_match",
"evidence_grounded",
"confidence_calibrated",
"no_fabrication",
"no_mutation_claimed",
)Ein Fall besteht nur, wenn jeder dieser fünf wahr ist und der Score die Schwelle übersteigt, bewusst ein UND, kein ODER: Eine Diagnose kann sich als zuversichtliche, gut organisierte Prosa lesen (ein hoher Score) und trotzdem an einem harten Constraint scheitern, etwa eine Behauptung zu fabrizieren, die die Tools des Agenten nie hätten stützen können, und dieses Scheitern sollte den Fall versenken, egal wie gut der Text klingt. golden_set.json trägt sieben Fälle: eine saubere Ein-Signal-Diagnose, eine wirklich mehrdeutige, die der Agent ranken statt auflösen sollte, einen Fall ohne Evidenz, der ein explizites "insufficient evidence" produzieren sollte statt einer Vermutung, einen Noisy-Alarm-False-Positive, und einen Fall, der überhaupt nicht hypothetisch ist:
{
"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"
],
...
},Dieser Fall kam nicht von einem Whiteboard. Er kam aus einem Vorfall.
Der Tag, an dem sie log
Der Alarm war gewöhnlich: order-processor-errors, AWS/Lambda Errors für die order-processor-Funktion, bis zu 40 Prozent der Invocations über zehn Minuten. Der Triage-Agent tat, was er immer tut: rief cloudwatch_read auf, rief logs_read auf und kam mit einer Zusammenfassung zurück. Sein Befund, verdichtet: Die Bedrock-Invocation-Latenz auf dem Guardrail-behafteten Inference Profile des order-processors war im selben Fenster von 900 ms auf 2100 ms p99 gestiegen, und er benannte diesen Latenzanstieg als wahrscheinliche Root Cause, mit der Empfehlung, der Runbook-Agent solle sich das Lockern oder Neu-Tunen der Guardrails-Konfiguration ansehen.
Er lag falsch, und er lag auf die Art falsch, die am schwersten zu fangen ist: Die beiden Signale bewegten sich wirklich gemeinsam. Die Guardrails-Latenz war tatsächlich gestiegen. Die Korrelation war real. Was der eigene logs_read-Aufruf des Agenten ebenfalls zurückgegeben hatte, im selben Tool-Ergebnis sitzend, aus dem die Zusammenfassung angeblich gebaut war, war eine Serie von ProvisionedThroughputExceededException-Einträgen gegen die idempotency-keys-DynamoDB-Tabelle, unmittelbar vor jeder fehlgeschlagenen Invocation. Die Evidenz für die tatsächliche Ursache war bereits im Transkript. Die Zusammenfassung hat sie nur nicht gewichtet, griff nach der zuletzt diskutierten, sichtbareren Korrelation (Guardrails war erst zwei Teile zuvor ausgeliefert worden) statt nach der Log-Zeile, die tatsächlich diagnostisch war.
Niemand fing es in Echtzeit, weil die Plattform an diesem Punkt des Aufbaus Traces davon hatte, was der Agent aufrief, nicht Traces davon, was die eigenen Tool-Ergebnisse des Agenten enthielten gegenüber dem, was seine Zusammenfassung behauptete. Der Fehlgriff tauchte im Postmortem auf, dem menschlichen, als ein Engineer dieselbe Logs-Insights-Query von Hand erneut ausführte und die DynamoDB-Fehler direkt dort sitzen sah. Denselben Review über den Agenten laufen zu lassen, nicht nur über den Vorfall, ist der Punkt dieses Abschnitts: Der Trace zeigte exakt, welcher Tool-Aufruf die disqualifizierende Evidenz zurückgab, und exakt, welcher Satz in der finalen Zusammenfassung sie nicht erwähnte, was der Unterschied ist zwischen "der Agent lag falsch" und "hier ist die präzise Lücke zwischen dem, was er sah, und dem, was er sagte", die einzige Version dieses Satzes, auf die sich zu handeln lohnt. Der Fix war kein schlaueres Modell. Es war incident-003, wörtlich aus dem eigenen Transkript dieses Vorfalls ins Golden Set aufgenommen, sodass eine Prompt-Änderung, ein Tool-Schema-Edit oder ein Modell-Swap, der denselben Fehler wieder einführt, jetzt in CI scheitert, bevor er Produktion erneut erreicht.
Serien-Retrospektive: bewertet gegen Teil 1
Teil 1 schloss mit sechs Zahlen unter "wie fertig aussieht". Ein Finale, das nicht zu ihnen zurückkehrt, ist nur eine Demo. Hier ist, wo die Plattform tatsächlich gelandet ist, ehrlich, nicht in der Version, die sich am besten liest:
- Mediane Zeit von Page zu angereicherter Slack-Zusammenfassung: runter auf rund 6 Minuten, von ursprünglich 25 bis 35. Echter Fortschritt, der das Unter-5-Minuten-Ziel verfehlt; die verbleibende Lücke ist größtenteils die Knowledge-Base-Retrieval-Latenz des Runbook-Agenten auf den größten Einträgen des Runbook-Korpus, eher ein Teil-7-Sizing-Problem als ein Architekturproblem.
- Null mutierende Aktionen ohne aufgezeichnete menschliche Freigabe: exakt erfüllt, null Ausnahmen, wöchentlich gegen CloudTrail verifiziert. Das ist das eine Kriterium, das von IAM statt von Disziplin durchgesetzt wird, und es ist das eine, das ohne Einschränkung gehalten hat.
- Monatlicher FinOps-Verzug von drei Wochen auf denselben Tag: erfüllt. Der tägliche Cost-Sweep plus die AWS Budgets und Cost Anomaly Detection dieses Teils, mit Kostenzuordnungs-Tags pro Agent, ist das, was diesen Punkt tatsächlich schließt; eine Forecast-Schwellen-Benachrichtigung Tage vor dem alten Monatsreview ist der ganze Sinn.
- Runbook-Veraltung von etwa einem von drei auf unter eines von zwanzig: verbessert auf rund eines von zwölf, nicht ganz am Ziel. Drift kontinuierlich zu fangen schlägt ein Jahres-Audit, aber "kontinuierlich" heißt weiterhin, dass jemand die markierten Runbooks reviewt, und diese Review-Queue ist noch nicht so schnell wie die Erkennung.
- Vierteljährlicher Game Day, Ops-Tooling vollständig deaktiviert: einmal durchgeführt, erfolgreich. Paging und manuelle Runbook-Ausführung funktionierten exakt wie vor der Existenz der Plattform, ohne dass eine stille Abhängigkeit entdeckt wurde. Ein Datenpunkt, noch keine Erfolgsbilanz.
- Incident-Triage-Diagnose, die die Root Cause oft genug trifft, dass On-Call aufhört, von Hand nachzuprüfen: Das ist das Kriterium, für das dieser Teil eine numerische Schwelle definieren sollte, und jetzt hat es eine: fünf harte Constraints plus ein Score von 80 oder höher, evaluiert gegen das Golden Set. Sechs von sieben Fällen haben sie bei der ersten Messung geschafft. Der siebte war
incident-003, und genau das ist der Punkt: Die Aufgabe der Schwelle ist nicht, eine saubere Zahl zu melden, sie ist, den Fall zu fangen, der noch nicht sauber ist.
Was wir anders machen würden
Den Eval-Harness ab Teil 3 bauen, nicht ab Teil 8. Ein Golden Set mit auch nur drei Fällen, so dünn es wäre, vom Tag an, an dem der erste Agent shippte, hätte die Diagnosequalitäts-Regression, die später incident-003 wurde, gefangen, bevor sie je einen echten Vorfall erreichte, nicht danach. Zweitens gehören Custom-OTEL-Spans vom ersten Tag an in den eigenen Code eines Agenten, nicht als Teil-8-IAM-Grant an eine Rolle geschraubt, die seit Monaten ohne sie läuft; die Lücke zwischen "der Agent hat ein Tool aufgerufen" und "die Zusammenfassung des Agenten entsprach dem, was das Tool zurückgab" ist exakt die Lücke, die hier zählte, und sie hätte vom ersten Deploy an sichtbar sein sollen. Drittens sollten Dead-Letter-Queues auf asynchronen Hops ein Teil-4-Anliegen sein, verdrahtet zusammen mit dem Step-Functions-Approval-Gate, kein Muster, das dieser Teil auf dem einen asynchronen Hop einführt, der ihm zufällig ganz gehört. Nichts davon sind große Reue-Punkte. Sie sind die gewöhnlichen Kosten davon, die Plattform in der Reihenfolge zu bauen, in der es Sinn ergab, sie zu bauen, was nicht immer die Reihenfolge ist, die jeden Fehler am frühesten gefangen hätte.
Weiterlesen
- Teil 7, Sizing: Die Token-Rechnung, die niemand vorab macht, für die Durchsatz- und Kostenrechnung, die die Budgets und Cost Anomaly Detection dieses Teils jetzt in Produktion beobachten, statt sie drei Wochen zu spät zu erwischen.
- Teil 1, Das Szenario: Warum ein Ops-Team Agenten einstellt, wo diese Serie begonnen hat und wo die sechs oben bewerteten Zahlen zuerst aufgeschrieben wurden.
- Monitoring EC2 Disk Space with a Simple Bash Script and Slack Alerts auf ercan.cloud, dieselbe Alert-nach-Slack-Form, auf die das SNS-Topic und die Notifier-Lambda dieses Teils hinbauen, von einem Single-Host-Skript aus statt von einer Multi-Agent-Plattform.
Das vollständige Modul terraform/40-observability/ und evals/, einschließlich der anderen sechs Golden-Set-Fälle und der Notifier-Lambda, die aus den Ausschnitten oben gekürzt wurden, liegen im begleitenden Repository unter github.com/flightlesstux/agents-on-call. Für die Datenbank- und Infrastruktur-Seite, solche Plattformen in diesem Maßstab zu betreiben, gibt es die Field Notes auf ercan.cloud, und der Hub liegt auf ercanermis.com.
Weiteres von Ercan
Zwei weitere Seiten, gleicher Autor, anderes Terrain.
Cloud, AWS, EKS, Terraform, Platform Engineering.
Praxisnotizen aus Produktionssystemen. EKS, IAM, Terraform im Organisationsmaßstab, Observability, Kostenoptimierung.
Besuchen ercan.cloud →Die Drehscheibe. Über mich, Beratung, Kontakt.
Persönliche Drehscheibe für beide Schreibspuren. Wer ich bin, wie die Beratung funktioniert, wie Sie mich erreichen.
Besuchen ercanermis.com →