ProductionAI/LLMAgentic AIAWS BedrockLangGraph
ESG Agent
Ask it a question about your sustainability data and it answers, builds the dashboard, or produces the Excel or PDF report — and it cannot make a number up, because it never calculates one.
21
Typed Tools, One Agent
Zero
Model-Computed Figures
RBAC
Enforced In Tool Binding
Client work under NDA — product name, code, and live demo cannot be shared. This write-up covers my architectural approach only.
The Problem
Sustainability reporting runs on numbers that are scattered by nature. ESG (Environmental, Social, Governance) metrics live across databases, uploaded spreadsheets, and PDF filings, each with its own structure and its own terminology for the same underlying concept.
Answering a single executive question — "how did our Scope 2 emissions move quarter over quarter?" — means running several queries by hand, cross-referencing reports, and building a chart manually. It is slow, error-prone, and does not scale.
I wanted a system where an analyst could simply ask, and get an answer they could defend in a compliance review.
Why It Was Hard
A conversational layer over regulated data has far less tolerance for error than a typical chatbot.
The core challenges were: (1) Accuracy is non-negotiable — ESG figures feed compliance reporting, so an answer that is approximately right is worse than no answer, because it carries false confidence. (2) LLMs are poor at arithmetic — asking a model to compute emissions totals or period-over-period deltas invites silent numerical error. (3) Access boundaries — different roles are entitled to different slices of data, and that boundary has to hold inside a free-form chat interface where users can phrase requests any way they like. (4) Open-ended intent — users do not just ask questions, they ask for dashboards, exports, and comparisons, each needing a different capability. (5) Conversational continuity — the fifth question in a session usually refers back to the second, so whatever architecture serves it has to keep one unbroken thread of what "that figure" and "the same thing" mean.
That last constraint is what settled the shape of the system.
The Decision: One Agent, Not Many
The default answer in 2024–25 was a multi-agent system: a supervisor that classifies the request and hands off to an emissions specialist, a benchmarking specialist, an export specialist. I deliberately built a single agent with 21 typed tools instead. That is the decision the rest of the architecture follows from.
Two reasons, both learned the expensive way:
→ A router is itself a model call — with less context than the agent it routes to. You do not remove a failure mode by adding a supervisor; you add one, and you put it in front, where its mistakes are unrecoverable. The router sees a truncated view of the conversation and has to guess intent before any tool has run. When it guesses wrong, the specialist it picked answers confidently in the wrong domain, and nothing downstream can detect that.
→ Every handoff loses conversational continuity, quietly. "Now show me the same thing for our Pune site" only means something if you still hold what "the same thing" was. Passing state between agents means serialising an understanding that was never fully written down — what the user actually meant, what got clarified three turns ago, which figure they are implicitly comparing against. The handoff appears to succeed. The answer is subtly about something else.
Multi-agent earns its complexity when sub-tasks are genuinely independent and long-running. Conversational analytics is the opposite: one continuous thread where turn six depends on turn two. So: one agent, one conversation, one state — and the specialisation moved into the tools rather than into more models.
Role-Scoped Tool Binding
With a single agent, authorisation cannot live in a specialist's prompt — so it lives in what the agent is even allowed to see.
Tools are bound per request, resolved server-side from the caller's role. The agent is not given 21 tools and told which ones it may use; it is constructed with only the tools that caller is entitled to. A user without export rights is not holding a tool that refuses — the export tool is not in the schema, so the model cannot select it, cannot mention it, and cannot be argued into it.
This matters because the alternative — "you may only use tool X if the user is an admin" written in a system prompt — is a request, not a boundary. Prompt-level rules are negotiable by anyone willing to rephrase. A tool absent from the binding is not.
The same resolution runs on parameters. A tool bound for a site-level user has its scope pre-filled server-side; the model never supplies the facility filter, so it cannot widen it. Authorisation is decided before the model is invoked, not evaluated after it produces a call.
Server-Derived Figures
The model never computes a number that reaches a user.
Every tool returns figures calculated server-side from source records. The agent selects a tool, parameterises it, receives a validated result set, and phrases it. It does not add, average, or compute a period-over-period delta — not because it usually gets those wrong, but because when it does, the output is fluent and the error is invisible.
This is the decision that makes the system defensible in a compliance review. The audit question is never "did the model reason correctly?" — an unanswerable question — but "which query produced this figure?", which has an exact answer and a re-runnable trace.
Dashboards and spreadsheet/PDF exports are generated from the same validated result sets that back the chat replies. A chart and a chat answer cannot disagree, because they are two renderings of one computation.
Stack: AWS Bedrock for inference, LangGraph for the tool-calling loop and intermediate state across multi-step requests ("compare these two sites, then chart the gap"), MongoDB underneath.
Design Tradeoffs
The single-agent choice has a real cost: 21 tools in one schema is a large surface for one model to select across, and tool descriptions have to be written as carefully as prompts to keep neighbouring tools distinguishable. A supervisor would have narrowed that choice at each step. I accepted the wider selection problem because it is observable — a wrong tool call shows up in the trace and can be fixed by sharpening a description — whereas a wrong routing decision is silent and structural.
Tool granularity was the other trade worth getting right. Broad, overloaded tools are tempting because there are fewer to maintain, but they make selection ambiguous and parameters inconsistent. Narrow, single-purpose functions with strict schemas are more to build and far more reliable in practice — models choose well between precise options, and poorly within one flexible one. That is what drives the count to 21 rather than six.
The instinctive alternative to all of this is to retrieve context and let the model read figures out of it. For regulated reporting that is the wrong shape: fluent output makes arithmetic mistakes hard to spot, and a confident wrong total is more dangerous than an obvious failure. Moving every calculation server-side costs some flexibility but buys correctness you can actually audit.
Results
The system runs in production for enterprise sustainability teams:
• 21 typed tools bound per request against one continuous conversation
• Natural-language querying, dashboard generation, and data exports in one interface
• Role-based access enforced in the tool binding, not the prompt
• No hallucinated figures by construction — every number is server-derived
• Roughly 70% less time spent on manual ESG analysis, by the team's own estimate of the workflow it replaced
Analysts ask in plain language and get a grounded answer, a dashboard, or an export, instead of assembling each of those by hand.
Key Learnings
1. Adding an agent adds a model call, not a guarantee. A router is another chance to be wrong, placed where it can least be recovered from. Reach for multi-agent when sub-tasks are truly independent — not to organise a conversation.
2. Handoffs lose context silently. The failure is not a crash; it is an answer that is subtly about the wrong thing. Single-threaded state is worth a lot in a conversational product.
3. Let the LLM route, not compute. For regulated data the model should choose which trusted operation to run and phrase the result — never produce the numbers itself.
4. Many narrow tools beat few broad ones. Precise, single-purpose functions with strict schemas dramatically improve tool-selection accuracy.
5. Enforce authorisation by what the model can see. A rule in a prompt is a suggestion; a tool absent from the binding is a guarantee.
6. Fluency hides errors. A confident, well-written wrong answer is harder to catch than an obviously broken one, so correctness has to be structural rather than reviewed after the fact.