AgentC: Autonomous Carbon Manager
2026-06-16 · AI Engineering · LangGraph · FastAPI · Claude · OpenAI · ChromaDB · LangSmith · Next.js · IoT

AgentC is my MSc dissertation: a multi-agent system that manages an organisation's Scope 2 carbon emissions without a human driving each step. It reads energy data off IoT sensors, works out what the emissions actually are, proposes a plan to reduce them, checks that plan against comfort and safety constraints, simulates it, and commits it as scheduled actions.
The interesting problem was not the model. It was making a system of nine workers and four planning specialists reach a defensible answer, and being able to show afterwards how it got there.
One manager, one worker at a time
The graph is a supervisor pattern. A manager agent holds the state and decides the next step; every worker runs, writes back to shared state, and returns to the manager. No worker calls another worker. That single rule is what keeps a nine-worker system debuggable, because at any moment exactly one node is running and the manager's routing decision is recorded.

The nine workers are data collection, emission factor resolution, IoT data processing, baseline estimation, usage pattern analytics, pattern learning, simulation, goal assessment, and commit. Three of them can call tools and re-enter themselves until they stop asking for one.
Planning is a sub-graph, not a prompt
Producing the reduction plan is a separate LangGraph sub-graph with four specialists: optimization proposes, comfort challenges it, validation checks it against constraints, and scheduling turns the approved plan into timed actions.
Both critics can send the plan back to be reworked, and both loops are bounded at three attempts. If validation still refuses after three, the run does not quietly return a bad plan. It routes to error handling and fails with the recorded violations. Knowing when to fail is most of what makes an autonomous system safe to leave running.
Every number carries its provenance
An emission factor can come from the organisation's own records, from a document in the vector store, from a web search, or from a default. That difference matters enormously to a carbon report, so the resolver returns a typed result where each factor carries both where it came from and how confident the system is in it:
1class EmissionFactorEntry(BaseModel):
2 emission_factor: float
3 provenance: Literal["existing", "vector", "web", "default"]
4 confidence: Literal["high", "medium", "low", "unknown"]Nothing downstream has to guess whether a figure was authoritative or scraped. The arithmetic itself never happens inside the model: emissions and simulations are computed by deterministic tools, and the agents decide what to compute, not what the answer is.
Watching it run
Shared state is a typed schema every node reads and writes, checkpointed with LangGraph so a run can be resumed. On top of that, each node execution and each edge traversal is written to Postgres, which is what lets the Next.js dashboard draw the agent graph as it executes rather than after the fact. LangSmith traces every step, tool and token, so cost and latency are attributable to a specific node instead of to the run as a whole.
Stack
LangGraph and LangChain on FastAPI, Postgres for application state, InfluxDB for IoT time series, ChromaDB for retrieval, Node-RED for sensor ingestion, Next.js for the dashboard, LangSmith for tracing, deployed on Railway. Around 30 tools across 6 modules. Model choice is per node rather than global, mixing Claude and GPT depending on whether the node needs reasoning or cheap structured extraction.
What I would do differently
The bounded retry counts are a blunt instrument. A plan rejected three times for the same violation is a different situation from one rejected for three different reasons, and the system treats them identically. I would also push more of the manager's routing into explicit typed transitions rather than a string returned in state.
The source stays private until convocation. Happy to walk through it.