From Dashboards to Decisions: Building an AI-Powered Microservices Troubleshooting System
Using Strands Agents, CloudWatch and a locally hosted Llama 3.1 8B model on Amazon EC2
What if your monitoring system could do more than tell you that something is broken?
What if it could investigate the evidence, connect related failures, explain what is likely happening, recommend the next action — and, for carefully controlled cases, even remediate the problem?
This is the problem I wanted to explore in my microservices environment.
Collecting data is easy. Turning that data into a useful engineering decision is much harder.
The next question naturally followed:
Can we use an AI agent to reduce the amount of manual investigation engineers need to perform after an alert?
This article describes how I approached that problem using Strands Agents, AWS CloudWatch, Streamlit, and a locally hosted Llama 3.1 8B model running on Amazon EC2.
The model was deliberately not a paid API.
More importantly, this experiment wasn’t just about adding an LLM to CloudWatch. It was about exploring what happens when we introduce an agentic investigation and remediation layer into a real microservices architecture.
Situation: We already had observability
In a microservices environment, observability produces a tremendous amount of information.
Depending on the platform, we may have:
- multiple microservices
- ECS workloads
- CloudWatch logs
- metrics
- alarms
- dashboards
- deployment information
- application errors
- dependency failures
- queue activity
- infrastructure events
On paper, this gives engineers everything they need to troubleshoot an incident.
But there is an important distinction:
Having the data doesn’t mean the problem is easy to diagnose.
A typical incident might look like this:
| |
The dashboard might tell me:
orders-serviceerror rate has increased.
CloudWatch might tell me:
Connection timeout
But the engineer still has to ask:
- Is this the root cause or a symptom?
- Which dependency is failing?
- Did this start after a deployment?
- Are other services seeing the same problem?
- Has this happened before?
- What should I investigate next?
- Can anything safely be remediated?
That investigation can consume a significant amount of engineering time.
The problem wasn’t a lack of observability.
The problem was the investigation loop.
Task: Move from detection to investigation
I wasn’t trying to build another monitoring dashboard.
The objective was to introduce an AI-assisted investigation layer on top of the existing observability platform.
The intended workflow was:
| |
The objective wasn’t to replace engineers.
It was to automate the repetitive first stage of troubleshooting:
Collect the evidence → correlate it → summarize it → form an initial hypothesis.
The engineer would still make the final decision.
Action: Introduce an agent into the existing architecture
Rather than building a completely separate AI platform, I wanted the agent to sit naturally on top of the existing microservices and observability architecture.
The initial design was:
| |
This is where Strands Agents became particularly interesting.
The AWS Builder CloudWatch Analyzer example demonstrates a similar architecture: a Strands Agent uses tools to retrieve CloudWatch information and produce an analysis. The reference implementation uses a managed model through Amazon Bedrock. AWS Builder — Automating CloudWatch Log Analysis with Amazon Strands Agent
I wanted to take that idea in a different direction.
The model didn’t need to be a paid API
The obvious architecture would have been:
| |
That is a perfectly valid production architecture.
But I had another objective.
I wanted to learn more than simply:
“How do I call an LLM API?”
I wanted to understand:
“What does it take to actually operate a model as part of an application?”
So I deliberately replaced the managed model layer with:
| |
The resulting architecture became:
| |
This separation is important.
Strands handles the agent orchestration.
Tools provide access to operational data.
Python handles deterministic processing and calculations.
Ollama serves the model.
Llama 3.1 8B performs reasoning and synthesis.
CloudWatch remains the source of operational truth.
Why I didn’t simply send all the logs to the LLM
A naive implementation could look like this:
| |
I didn’t want that architecture.
Production logs are noisy.
They contain:
- repeated messages
- health checks
- warnings
- stack traces
- retries
- unrelated events
- duplicate errors
- enormous amounts of contextual information
Sending everything to the model creates three problems:
- More data to process
- More latency
- More opportunity for the model to get distracted
Instead, I treated CloudWatch as a set of tools available to the agent.
The agent could retrieve the information it needed.
Conceptually:
| |
That distinction is what turns a simple LLM application into an agentic workflow.
Tools are the bridge between the agent and reality
The agent doesn’t need to understand how CloudWatch works internally.
It needs capabilities.
For example:
| |
Conceptually, the agent sees:
| |
This gives us an important architectural boundary:
| |
If I later want to add:
- deployment history
- GitHub commits
- runbooks
- service ownership
- incident history
- Kubernetes information
I can add capabilities without redesigning the entire application.
Deterministic processing before AI reasoning
Another important design decision was keeping deterministic work outside the LLM.
For example, suppose CloudWatch contains thousands of log events.
Python can perform:
- filtering
- aggregation
- counting
- timestamp normalization
- grouping
- error classification
- threshold calculations
The model doesn’t need to calculate:
“There were 142 errors.”
Python can calculate it.
The model can then reason about what those 142 errors mean.
The pipeline becomes:
| |
This became one of the most important principles of the implementation:
Don’t ask the LLM to do something deterministic that Python can do reliably.
From errors to causal chains
Consider a simplified incident:
| |
A dashboard might display all three.
But the engineer has to correlate them.
The agent can help construct a hypothesis:
| |
Notice the language:
Observed
versus
Likely
versus
Potential
This distinction matters.
I don’t want an LLM saying:
“The root cause is definitely X.”
when the available evidence only supports:
“X is currently the most likely explanation.”
That is an important safety boundary.
The output should be useful to an engineer
The goal isn’t to produce a beautiful AI paragraph.
A useful investigation report should answer:
What happened?
| |
What evidence supports it?
| |
What is the likely cause?
| |
What is uncertain?
| |
What should I investigate next?
| |
That is much more useful than:
“There seems to be a problem with payment-service.”
The first result: reducing the investigation loop
The practical difference is the workflow.
Before
| |
After
| |
The engineer still makes the decision.
But much of the repetitive information gathering is automated.
Then came the obvious question
Once the agent could investigate a problem, I asked:
If the agent knows what is wrong, why not let it fix it?
Technically, there is nothing preventing us from giving an agent tools that can modify AWS resources.
For example:
| |
And this is where the project became much more interesting.
Because agentic remediation introduces a completely different risk profile.
Investigation is not the same as remediation
There is a huge difference between:
“The service appears unhealthy.”
and:
“I restarted 50 production tasks.”
The first is an analytical conclusion.
The second is a production action.
An agent that can read has a very different risk profile from an agent that can write.
I therefore started thinking about tools in two categories:
| |
This led to an important design principle:
Start with read-only autonomy. Earn write autonomy through evidence.
A real example: DLQ remediation
Consider a microservices application consuming messages from SQS.
A DLQ starts accumulating messages.
An agent might discover:
| |
The agent might recommend:
Do not redrive the messages yet. The consumer appears to have a deterministic application error.
That is a much better outcome than blindly redriving the DLQ.
Because otherwise:
| |
We have automated a loop rather than fixing the problem.
This is exactly why autonomous remediation needs context and verification.
Controlled remediation
Rather than immediately allowing:
| |
I prefer:
| |
For example:
| |
The agent becomes an experienced engineer’s assistant rather than an uncontrolled automation engine.
Not every remediation has the same risk
I would classify remediation actions by blast radius.
🟢 Low risk
Examples:
- collect additional logs
- retry a diagnostic query
- retrieve service status
- inspect deployment history
- restart a single demonstrably unhealthy task
🟡 Medium risk
Examples:
- restart multiple tasks
- scale a service
- redrive messages
- disable an unhealthy target
🔴 High risk
Examples:
- modify infrastructure
- change security configuration
- modify IAM
- change database configuration
- delete resources
- modify production deployment configuration
The agent may be able to recommend all three categories.
That doesn’t mean it should be allowed to execute all three.
The principle I want to follow
I see agent autonomy as a progression:
| |
This is a much safer way to introduce agentic automation into production systems.
Don’t start with:
“Give the AI production access.”
Start with:
“Give the AI read access and prove that its investigations are useful.”
Then progressively increase autonomy.
Remediation isn’t complete until we verify it
Another important realization was that executing an action isn’t the end of the workflow.
Suppose the agent restarts an ECS task.
A bad workflow would be:
| |
A better workflow is:
| |
This is a critical distinction.
Otherwise we’re automating actions rather than automating operations.
The MLOps problem begins here
Once the agent starts making operational recommendations, simply asking:
“Does the answer look good?”
isn’t enough.
I want to capture the complete decision chain:
| |
For example:
| |
But we also need negative examples:
| |
This data is extremely valuable.
It becomes an evaluation dataset for the agent.
From AI demo to measurable engineering system
This is where I see the project evolving beyond a simple LLM integration.
The long-term loop becomes:
| |
Now I can start measuring:
- investigation accuracy
- root-cause hypothesis accuracy
- false positives
- false negatives
- recommendation quality
- remediation success rate
- time to first useful hypothesis
- mean time to resolution
- human approval rate
- rollback rate
- model latency
- inference failures
This is where MLOps becomes relevant.
The model isn’t just producing text.
It is participating in an operational workflow whose outcomes can be measured.
Where I want to take the architecture
The current implementation is intentionally small.
But the architecture gives me a path to evolve it.
Current
| |
Next
| |
Eventually
| |
Adding memory and RAG
Another limitation of the first version is that the agent only knows what it can observe during the current investigation.
But experienced engineers don’t work that way.
They remember:
“We’ve seen this incident before.”
“This service has a history of connection pool issues.”
“The last time this happened, the deployment was rolled back.”
That leads naturally to the next evolution:
| |
Eventually, memory can capture the operational history of the system.
The agent can then move from:
“What is happening?”
to:
“What is happening, have we seen it before, and what worked last time?”
Why the local model mattered
It would be easy to focus on the fact that I hosted Llama 3.1 8B on EC2.
But that isn’t really the main point.
The model choice forced me to think about the entire inference stack.
With a managed model:
| |
A significant amount of infrastructure is abstracted away.
With a self-hosted model:
| |
Now I have to consider:
- instance sizing
- memory
- inference latency
- model loading
- concurrency
- networking
- security groups
- authentication
- availability
- model versioning
- health checks
- monitoring
- compute cost
That was exactly the learning opportunity I wanted.
The experiment changed from:
“How do I call an LLM?”
to:
“How do I operate an LLM as part of a production-oriented engineering system?”
The bigger architecture
Putting everything together, the direction looks like this:
| |
The interesting part isn’t any individual component.
It’s the progression:
Observability → Evidence → Agent → Reasoning → Recommendation → Controlled Action → Verification → Learning
What I learned
There are several lessons I took away from this implementation.
1. An agent is not just an LLM with a prompt
The useful abstraction is:
| |
The model becomes one component of the system.
2. Good tools matter more than clever prompts
If the agent has poor access to operational data, a better prompt won’t solve the problem.
If the tools provide:
- accurate logs
- useful metrics
- service context
- deployment information
- structured evidence
the model has a much better foundation for reasoning.
3. Deterministic work should remain deterministic
Calculations, filtering, aggregation and validation don’t need an LLM.
Let Python do them.
Let the model focus on:
- reasoning
- correlation
- summarization
- hypothesis generation
- explanation
That makes the overall system easier to control.
4. Local models are an architectural choice
Running the model myself wasn’t simply about avoiding API costs.
It gave me ownership of:
| |
That is a much broader engineering problem.
And that is exactly why I wanted to do it.
5. Autonomous remediation should be earned
The most important lesson may be this one.
An agent that can read production data is useful.
An agent that can modify production systems is powerful.
An agent that can modify production systems without appropriate controls is dangerous.
So I don’t see autonomy as a binary switch.
I see it as a progression:
| |
Result: From dashboards to controlled autonomy
This brings me back to the idea behind my original Dashboards to Decisions work.
The progression is now:
| |
A dashboard tells me:
Something is wrong.
An agent can help answer:
What appears to be happening, what evidence supports it, and what should I investigate next?
A mature agentic system could eventually go further:
Here is the recommended remediation, here is the blast radius, here is why I believe it is safe, and here is what I will verify afterward.
That is a much more interesting direction than simply putting a chatbot on top of CloudWatch.
Closing: The model is not the product
The most important takeaway from this project isn’t:
“I hosted Llama 3.1 8B on EC2.”
That was one implementation decision.
The bigger lesson is that the interesting engineering problem sits around the model.
I already had:
Microservices → Observability → Dashboards → Alerts
The next step was:
Observability → Evidence → Agent → Investigation → Decision Support
And the next evolution is:
Decision Support → Controlled Remediation → Verification → Learning
Hosting the model myself allowed me to explore much more of that stack instead of treating inference as a black box.
It also reinforced something I increasingly believe about Agentic AI in engineering:
The hard problem isn’t making an agent take an action.
The hard problem is deciding which actions it should be allowed to take.
A production agent needs more than intelligence.
It needs:
- bounded tools
- least-privilege permissions
- guardrails
- approval mechanisms
- blast-radius controls
- auditability
- verification
- evaluation
- rollback strategies
That’s where Agentic AI starts becoming an engineering discipline, rather than simply an LLM integration.
And that is ultimately what I wanted to explore with this implementation.
The dashboard tells you where to look.
The agent helps you investigate why.
The engineer decides what should happen next.
And eventually, carefully controlled automation can take care of the safe parts.
That, for me, is the next step in going from dashboards to decisions — and from decisions to controlled autonomy.