<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Elastic Observability Labs - Articles by Naga Putta</title>
        <link>https://www.elastic.co/observability-labs</link>
        <description>Trusted security news &amp; research from the team at Elastic.</description>
        <lastBuildDate>Thu, 09 Jul 2026 18:30:35 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>Elastic Observability Labs - Articles by Naga Putta</title>
            <url>https://www.elastic.co/observability-labs/assets/observability-labs-thumbnail.png</url>
            <link>https://www.elastic.co/observability-labs</link>
        </image>
        <copyright>© 2026. Elasticsearch B.V. All Rights Reserved</copyright>
        <item>
            <title><![CDATA[From five dashboards to one prompt: Elastic Agent Builder's Red/Yellow/Green APM verdict]]></title>
            <link>https://www.elastic.co/observability-labs/blog/apm-health-check-elastic-agent-builder</link>
            <guid isPermaLink="false">apm-health-check-elastic-agent-builder</guid>
            <pubDate>Thu, 09 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Five ES|QL tools score latency, errors, throughput and dependencies to find the root cause, so you don't dashboard-hop during an APM incident.]]></description>
            <content:encoded><![CDATA[<p>Ask Elastic's new APM Service Health Monitor whether your service is healthy. It fans out to five ES|QL queries over your existing <code>traces-*</code> data and answers Red, Yellow, or Green with the root cause attached, in one response, without switching dashboards or correlating anything by hand. It runs on Elastic Agent Builder, tested on Elasticsearch and Kibana 9.3, and one deployment covers every service in your fleet with no per-service setup. Here's how it's built.</p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/apm-health-check-elastic-agent-builder/image2.png" alt="" /></p>
<h2>Is my service healthy? One question, one agent, one answer</h2>
<p>There is a question every engineer dreads during an incident: &quot;Is the service healthy?&quot;</p>
<p>It sounds simple. It is not. Answering it properly means switching between dashboards, firing off multiple queries, correlating latency spikes with error logs, and checking whether downstream dependencies are contributing to the problem, all under pressure and often at an inconvenient hour.</p>
<p>What if that entire investigation collapsed into a single conversation?</p>
<p>That is what we set out to build: an AI agent that behaves like a knowledgeable SRE sitting beside you. It knows which questions to ask, knows how to query your APM data, and returns a clear Red, Yellow, or Green health verdict with context and recommendations. We call it the APM Service Health Monitor, and it runs entirely on Elastic.</p>
<h2>Why APM health is a derived signal, not a single metric</h2>
<p>Here is the thing about APM data: you already have everything you need. Latency percentiles, error rates, throughput, and span-level dependency traces are all sitting in your <code>traces-*</code> indices, indexed and ready.</p>
<p>The missing piece is not data. It is the reasoning layer that connects those signals into a coherent picture.</p>
<p>A p95 latency of 890 ms means very little on its own. But a p95 latency of 890 ms that runs 38% above the 24-hour baseline, alongside a 6% error rate on your postgres-primary dependency? That is a story. That is a Red, and it tells an engineer exactly where to look.</p>
<p>The APM Service Health Monitor encodes that reasoning (threshold logic, trend comparison, and dependency blast-radius analysis) into an Elastic AI agent that runs it on demand, for any service, anytime.</p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/apm-health-check-elastic-agent-builder/image4.png" alt="APM Service Health Monitor agent at the centre, fanning out to five ES|QL tools querying traces-*" />
<em>APM Service Health Monitor agent at the centre, fanning out to five ES|QL tools querying traces-*</em></p>
<h2>How does the APM Service Health Monitor architecture work?</h2>
<p>The agent is registered in Elastic Agent Builder as a chat-type agent wired to five ES|QL-powered tools. Each tool does one thing precisely. The agent orchestrates, compares, and reasons across all five.</p>
<pre><code class="language-json">POST kbn:/api/agent_builder/agents
{
  &quot;id&quot;: &quot;apm_service_health_agent&quot;,
  &quot;type&quot;: &quot;chat&quot;,
  &quot;name&quot;: &quot;APM Service Health Monitor&quot;,
  ...
}
</code></pre>
<p>The APM Service Health Monitor sits at the center, fanning out to five ES|QL tools that query <code>traces-*</code>.</p>
<p>Here is how the tool layer is composed.</p>
<h3>Tool 1: apm_metrics_overview_tool, the 24-hour baseline</h3>
<p>Every health assessment starts with a baseline. This tool computes the aggregate picture for a service over the last 24 hours (average latency, p95 and p99 latency, error rate, and throughput) in a single ES|QL query:</p>
<pre><code>FROM traces-*
| WHERE service.name == ?service
| WHERE transaction.type == &quot;request&quot;
| WHERE @timestamp &gt;= NOW() - 24 hours
| EVAL is_error = CASE(event.outcome == &quot;failure&quot;, 1, 0)
| STATS
    avg_latency_ms  = AVG(transaction.duration.us / 1000),
    p95_latency_ms  = PERCENTILE(transaction.duration.us / 1000, 95),
    p99_latency_ms  = PERCENTILE(transaction.duration.us / 1000, 99),
    error_rate      = 100.0 * SUM(is_error) / COUNT(*),
    throughput_rps  = COUNT(*) / (24*3600)
</code></pre>
<p>This snapshot becomes the anchor. Every trend tool that follows compares its latest readings back to these numbers, so the agent always has a reference point, not just a raw value.</p>
<h3>Tool 2: apm_latency_trend_tool, performance over time</h3>
<p>Averages hide inflection points. The latency trend tool buckets p95, p75, and average latency into 5-minute intervals across the last 24 hours, giving the agent a time-series view of performance:</p>
<pre><code>| STATS
    avg_latency_ms = AVG(transaction.duration.us / 1000),
    p95_latency_ms = PERCENTILE(transaction.duration.us / 1000, 95),
    p75_latency_ms = PERCENTILE(transaction.duration.us / 1000, 75)
  BY time_bucket = DATE_TRUNC(5 minutes, @timestamp)
| SORT time_bucket ASC
</code></pre>
<p>The agent takes the most recent 5-minute bucket's <code>p95_latency_ms</code> and compares it against the <code>p95_latency_ms</code> from <code>apm_metrics_overview_tool</code>, the 24-hour rolling average. That percentage difference is what gets scored Green, Yellow, or Red.</p>
<h3>Tool 3: apm_error_trend_tool, failure shape detection</h3>
<p>Error rate is directional. A gradual rise from 0.5% to 1.2% tells a different story than a sudden spike to 8%. The error trend tool captures this shape with 5-minute buckets:</p>
<pre><code>| EVAL is_error = CASE(event.outcome == &quot;failure&quot;, 1, 0)
| STATS
    total_requests = COUNT(*),
    error_count    = SUM(is_error),
    error_rate     = 100.0 * SUM(is_error) / COUNT(*)
  BY time_bucket = DATE_TRUNC(5 minutes, @timestamp)
</code></pre>
<p>The agent reads the most recent bucket's <code>error_rate</code> from this trend and scores it directly against the fixed thresholds (below 1% Green, 1 to 5% Yellow, above 5% Red).</p>
<h3>Tool 4: apm_throughput_trend_tool, traffic as a health signal</h3>
<p>Throughput is underrated as a health indicator. A 40% drop in requests per second is itself an incident. It can mean a deployment regression, a misconfigured load balancer, or a silent upstream failure.</p>
<pre><code>| STATS requests_count = COUNT(*)
  BY time_bucket = DATE_TRUNC(5 minutes, @timestamp)
| SORT time_bucket ASC
</code></pre>
<p>The agent normalizes each 5-minute bucket's request count to a per-second rate and compares it against <code>throughput_rps</code> from <code>apm_metrics_overview_tool.</code>. The same 10% and 30% drift thresholds apply. A service that is suspiciously quiet is flagged just as quickly as one that is on fire.</p>
<h3>Tool 5: apm_dependency_health_tool, the blast-radius view</h3>
<p>The <code>apm_dependency_health_tool</code> is often the one that finds what the others cannot surface directly. A service can look internally healthy while a downstream database or external API silently accumulates failures. This tool maps every dependency via <code>span.destination.service.resource</code> and computes its error rate from span data:</p>
<pre><code>FROM traces-*
| WHERE processor.event == &quot;span&quot;
  AND span.destination.service.resource IS NOT NULL
| EVAL is_error = CASE(event.outcome == &quot;failure&quot;, 1, 0)
| STATS
    total_calls  = COUNT(*),
    failed_calls = SUM(is_error),
    error_rate   = 100.0 * SUM(is_error) / COUNT(*)
  BY dependency_name = span.destination.service.resource
| SORT error_rate DESC
</code></pre>
<p>Unlike the other four tools, dependency health is scored independently. Each dependency's <code>error_rate</code> is compared directly against fixed thresholds (1% or below Green, above 1% Yellow, above 5% Red), with no baseline comparison needed.</p>
<h2>How the health scoring works</h2>
<p>Once all five tools return their data, the agent computes a health state for each metric in a single reasoning step. There is no polling loop and no intermediate storage. Here is exactly what gets compared to what:</p>
<table>
<thead>
<tr>
<th align="left">Metric</th>
<th align="left">Tool providing the value</th>
<th align="left">Compared against</th>
<th align="left">How scored</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">Latency (p95)</td>
<td align="left"><code>apm_latency_trend_tool</code></td>
<td align="left">24h p95 from <code>apm_metrics_overview_tool</code></td>
<td align="left">&lt; 10% Green · 10–30% Yellow · &gt; 30% Red</td>
</tr>
<tr>
<td align="left">Error rate</td>
<td align="left"><code>apm_error_trend_tool</code></td>
<td align="left">Fixed thresholds + 24h <code>error_rate</code> from overview</td>
<td align="left">&lt; 1% Green · 1–5% Yellow · &gt; 5% Red</td>
</tr>
<tr>
<td align="left">Throughput</td>
<td align="left"><code>apm_throughput_trend_tool</code></td>
<td align="left">24h <code>throughput_rps</code> from <code>apm_metrics_overview_tool</code></td>
<td align="left">&lt; 10% Green · 10–30% Yellow · &gt; 30% Red</td>
</tr>
<tr>
<td align="left">Each dependency</td>
<td align="left"><code>apm_dependency_health_tool</code></td>
<td align="left">Fixed thresholds only (no baseline)</td>
<td align="left">All ≤1% Green · Any &gt;1% Yellow · Any &gt;5% Red</td>
</tr>
</tbody>
</table>
<p>The overall verdict follows a single rule: <strong>the worst individual metric wins</strong>.</p>
<pre><code>Any metric is Red                → Overall = 🔴 Red
Any metric is Yellow (no Red)   → Overall = 🟡 Yellow
All metrics are Green            → Overall = 🟢 Green
</code></pre>
<p>The Red/Yellow/Green scoring logic lives in the agent's natural-language instructions. It is readable, auditable, and adjustable without touching code or redeploying infrastructure.</p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/apm-health-check-elastic-agent-builder/image1.png" alt="Four metric columns - Latency, Error Rate, Throughput, Dependencies - converging into Red/Yellow/Green outcome states" />
<em>Four metric columns (Latency, Error Rate, Throughput, Dependencies) converging into Red/Yellow/Green outcome states</em></p>
<h2>Deploying the APM Service Health Monitor in Agent Builder</h2>
<p>Registration follows Elastic's standard Agent Builder API pattern, tested against Elasticsearch 9.3 and Kibana 9.3. Register the tools first, then wire the agent to all five tool IDs:</p>
<pre><code># Step 1: Register the five ES|QL tools
POST kbn:/api/agent_builder/tools   # apm_metrics_overview_tool
POST kbn:/api/agent_builder/tools   # apm_latency_trend_tool
POST kbn:/api/agent_builder/tools   # apm_error_trend_tool
POST kbn:/api/agent_builder/tools   # apm_throughput_trend_tool
POST kbn:/api/agent_builder/tools   # apm_dependency_health_tool
 
# Step 2: Register the agent with tools wired in
POST kbn:/api/agent_builder/agents  # apm_service_health_agent
 
# Step 3: Verify the agent is live
GET kbn:/api/agent_builder/agents/apm_service_health_agent
</code></pre>
<p>Each tool is parameterized with a service binding, so the same agent serves every service in your fleet with no per-service configuration. The queries target <code>traces-*</code> with cross-cluster wildcard support, so a single deployment covers multi-cluster environments out of the box.</p>
<h2>Try it: ask the agent about your service</h2>
<p>Once deployed, open the agent in Kibana and type this:</p>
<pre><code>What is the health of my checkout-service in the last 24 hours?
</code></pre>
<p>The agent fans out to all five tools, computes the metric states, applies the health logic in one pass, and responds with a structured report.</p>
<p><img src="https://www.elastic.co/observability-labs/assets/images/apm-health-check-elastic-agent-builder/image1.png" alt="Kibana chat card: checkout-service · Last 24h · per-metric table with Red/Yellow/Green status pills · Overall: Red" />
<em>Kibana chat card: checkout-service · Last 24h · per-metric table with Red/Yellow/Green status pills · Overall: 🔴 Red</em></p>
<p>The agent identified the root cause without any manual correlation: postgres-primary is failing 6.1% of its calls, and that is cascading directly into the p95 latency spike. No dashboard pivoting, no manual ES|QL. One prompt, full situational awareness.</p>
<p>You can continue the conversation with follow-up questions in the same session:</p>
<ul>
<li>Which other services depend on postgres-primary?</li>
<li>How does this compare to yesterday's health?</li>
<li>Show me the error trend for the last 6 hours only.</li>
</ul>
<p>The agent invokes the appropriate tools for each follow-up, keeping the full context of the original health assessment in view.</p>
<h2>Why Agent Builder and ES|QL are the right stack for this</h2>
<p>A few deliberate choices made this design work cleanly.</p>
<h3>ES|QL as the query layer</h3>
<p>ES|QL's pipe-based syntax makes each tool query readable, testable, and independently verifiable. The <code>DATE_TRUNC</code> bucketing for trends, the PERCENTILE aggregations for latency, and the span.destination.service.resource grouping for dependencies are precise, auditable queries. Run any of them directly in Kibana Dev Tools and you will see exactly what the agent sees.</p>
<h3>Narrow, stateless tools</h3>
<p>Each tool does one thing and returns structured data. The agent provides the orchestration and reasoning. Adding a new metric dimension means registering one new tool and updating the agent instructions. Nothing else changes.</p>
<h3>Instructions as a runbook</h3>
<p>The agent's health logic is expressed in natural language inside its configuration. Anyone on your team can read it, adjust it without a code deploy, and audit it in full from the Agent Builder UI in Kibana.</p>
<h2>What's next for the APM Service Health Monitor</h2>
<p>The APM Service Health Monitor is a foundation, not a ceiling. Natural extensions include:</p>
<ul>
<li>
<p><strong>Alert-triggered health checks:</strong> wire the agent to fire automatically when an anomaly detection rule triggers, attaching the health summary directly to the alert notification.</p>
</li>
<li>
<p><strong>Deployment correlation:</strong> integrate change event data so the agent can identify whether a Red status started after a specific deployment.</p>
</li>
<li>
<p><strong>SLO-aware thresholds:</strong> replace fixed percentage thresholds with per-service error budget consumption, so Red, Yellow, and Green reflect actual business impact against defined SLOs.</p>
</li>
<li>
<p><strong>Cross-service traversal:</strong> extend the dependency tool to recursively assess upstream and downstream services, building a full topology view from a single query.</p>
</li>
</ul>
<h2>Requirements and deployment</h2>
<p>Requirements:</p>
<ul>
<li>Elasticsearch 9.3 and Kibana 9.3 (tested version).</li>
<li><a href="https://www.elastic.co/docs/solutions/search/agent-builder/get-started">Agent Builder</a> enabled in Kibana.</li>
<li>APM data flowing into <code>traces-*</code> indices via any Elastic APM agent.</li>
<li><a href="https://www.elastic.co/subscriptions">Elastic Enterprise license</a> is required to utilize Agent Builder and <a href="https://www.elastic.co/search-labs/blog/esql-cross-cluster-search">ES|QL cross-cluster search</a>.</li>
</ul>
<p>The full agent and tool definitions are available in <a href="https://github.com/elastic/elasticsearch-labs/tree/main/supporting-blog-content/observability-labs/elastic-agent-apm-health-monitor">this repository</a>. Once your traces are indexed, all five ES|QL tools work against your data with no modification, parameterized by service name at query time, covering your entire service fleet from a single deployment.</p>
<p>The next time someone asks &quot;is the service healthy?&quot;, you will have a precise, data-backed answer before the question finishes echoing in the incident channel.</p>
<p>Questions, extensions, or feedback? Join the conversation in the <a href="https://discuss.elastic.co/">Elastic community forums</a>.</p>
<h2>Full deployment reference</h2>
<p>Everything you need to deploy the APM Service Health Monitor, in execution order. Run the five tool registrations first, then the agent.</p>
<p><strong>Tool 1: Dependency health</strong></p>
<pre><code>POST kbn:/api/agent_builder/tools
{
  &quot;id&quot;: &quot;apm_dependency_health_tool&quot;,
  &quot;type&quot;: &quot;esql&quot;,
  &quot;description&quot;: &quot;Evaluates the health of external dependencies (DBs, APIs, caches)...&quot;,
  &quot;configuration&quot;: {
    &quot;query&quot;: &quot;FROM traces-*\n| WHERE service.name == ?service\n| WHERE processor.event == \&quot;span\&quot;\n  AND span.destination.service.resource IS NOT NULL\n| WHERE @timestamp &gt;= NOW() - 24 hours\n| EVAL is_error = CASE(event.outcome == \&quot;failure\&quot;, 1, 0)\n| STATS\n    total_calls = COUNT(*),\n    failed_calls = SUM(is_error),\n    error_rate = 100.0 * SUM(is_error)/COUNT(*)\n  BY dependency_name = span.destination.service.resource\n| SORT error_rate DESC | LIMIT 100&quot;
  }
}
</code></pre>
<p><strong>Tool 2: Error trend</strong></p>
<pre><code>POST kbn:/api/agent_builder/tools
{
  &quot;id&quot;: &quot;apm_error_trend_tool&quot;,
  &quot;type&quot;: &quot;esql&quot;,
  &quot;description&quot;: &quot;Tracks 5-minute bucketed error rates for the service over 24 hours...&quot;,
  &quot;configuration&quot;: {
    &quot;query&quot;: &quot;FROM traces-*\n| WHERE service.name == ?service\n| WHERE transaction.type == \&quot;request\&quot;\n| WHERE @timestamp &gt;= NOW() - 24 hours\n| EVAL is_error = CASE(event.outcome == \&quot;failure\&quot;, 1, 0)\n| STATS\n    total_requests = COUNT(*),\n    error_count = SUM(is_error),\n    error_rate = 100.0 * SUM(is_error)/COUNT(*)\n  BY time_bucket = DATE_TRUNC(5 minutes, @timestamp)\n| SORT time_bucket ASC | LIMIT 1000&quot;
  }
}
</code></pre>
<p><strong>Tool 3: Latency trend</strong></p>
<pre><code>POST kbn:/api/agent_builder/tools
{
  &quot;id&quot;: &quot;apm_latency_trend_tool&quot;,
  &quot;type&quot;: &quot;esql&quot;,
  &quot;description&quot;: &quot;Provides 5-minute bucketed latency trends (avg, p95, p75) for last 24 hours...&quot;,
  &quot;configuration&quot;: {
    &quot;query&quot;: &quot;FROM traces-*\n| WHERE service.name == ?service\n| WHERE transaction.type == \&quot;request\&quot;\n| WHERE @timestamp &gt;= NOW() - 24 hours\n| STATS\n    avg_latency_ms = AVG(transaction.duration.us / 1000),\n    p95_latency_ms = PERCENTILE(transaction.duration.us / 1000, 95),\n    p75_latency_ms = PERCENTILE(transaction.duration.us / 1000, 75)\n  BY time_bucket = DATE_TRUNC(5 minutes, @timestamp)\n| SORT time_bucket ASC | LIMIT 1000&quot;
  }
}
</code></pre>
<p><strong>Tool 4: Metrics overview</strong></p>
<pre><code>POST kbn:/api/agent_builder/tools
{
  &quot;id&quot;: &quot;apm_metrics_overview_tool&quot;,
  &quot;type&quot;: &quot;esql&quot;,
  &quot;description&quot;: &quot;Aggregates key service metrics over last 24 hours: avg latency, p95/p99, error rate, throughput...&quot;,
  &quot;configuration&quot;: {
    &quot;query&quot;: &quot;FROM traces-*\n| WHERE service.name == ?service\n| WHERE transaction.type == \&quot;request\&quot;\n| WHERE @timestamp &gt;= NOW() - 24 hours\n| EVAL is_error = CASE(event.outcome == \&quot;failure\&quot;, 1, 0)\n| STATS\n    avg_latency_ms = AVG(transaction.duration.us / 1000),\n    p95_latency_ms = PERCENTILE(transaction.duration.us / 1000, 95),\n    p99_latency_ms = PERCENTILE(transaction.duration.us / 1000, 99),\n    error_rate = 100.0 * SUM(is_error) / COUNT(*),\n    throughput_rps = COUNT(*) / (24*3600)&quot;
  }
}
</code></pre>
<p><strong>Tool 5: Throughput trend</strong></p>
<pre><code>POST kbn:/api/agent_builder/tools
{
  &quot;id&quot;: &quot;apm_throughput_trend_tool&quot;,
  &quot;type&quot;: &quot;esql&quot;,
  &quot;description&quot;: &quot;Provides service throughput trends in 5-minute intervals over 24 hours...&quot;,
  &quot;configuration&quot;: {
    &quot;query&quot;: &quot;FROM traces-*\n| WHERE service.name == ?service\n| WHERE transaction.type == \&quot;request\&quot;\n| WHERE @timestamp &gt;= NOW() - 24 hours\n| STATS requests_count = COUNT(*)\n  BY time_bucket = DATE_TRUNC(5 minutes, @timestamp)\n| SORT time_bucket ASC | LIMIT 1000&quot;
  }
}
</code></pre>
<p><strong>Agent: APM service health monitor</strong></p>
<pre><code>POST kbn:/api/agent_builder/agents
{
  &quot;id&quot;: &quot;apm_service_health_agent&quot;,
  &quot;type&quot;: &quot;chat&quot;,
  &quot;name&quot;: &quot;APM Service Health Monitor&quot;,
  &quot;description&quot;: &quot;Provides Red/Yellow/Green health status and trends for services over 24 hours.&quot;,
  &quot;labels&quot;: [&quot;apm&quot;, &quot;health&quot;, &quot;service&quot;, &quot;monitoring&quot;],
  &quot;avatar_color&quot;: &quot;#4CAF50&quot;,
  &quot;configuration&quot;: {
    &quot;instructions&quot;: &quot;You are the Service Health Agent...&quot;,
    &quot;tools&quot;: [{
      &quot;tool_ids&quot;: [
        &quot;apm_dependency_health_tool&quot;,
        &quot;apm_error_trend_tool&quot;,
        &quot;apm_latency_trend_tool&quot;,
        &quot;apm_metrics_overview_tool&quot;,
        &quot;apm_throughput_trend_tool&quot;
      ]
    }]
  }
}
</code></pre>
]]></content:encoded>
            <category>observability-labs</category>
            <enclosure url="https://www.elastic.co/observability-labs/assets/images/apm-health-check-elastic-agent-builder/image4.png" length="0" type="image/png"/>
        </item>
    </channel>
</rss>