Home / Articles

Agentic AIOps With a Context Graph: Neocarta, Neo4j, LangGraph and Gemini

A closed-loop incident response design: ingest ServiceNow, Dynatrace and GitHub into Neo4j, reason with LangGraph and Gemini behind a safety gate, and write every action back.

Publisher: REACTAPP.TOP10 min read2402 words

This article is published in English.

Sources: Building Agentic AIOps: A Closed-Loop Platform with LangGraph, Gemini, and a Native Graph Database

During an outage on a platform or SRE team, the pattern repeats. One engineer follows Dynatrace traces, another combs GitHub deploy history for what changed, and a third edits the ServiceNow ticket so that leadership stops asking for updates. Nobody holds the whole picture because it exists nowhere in particular. It is spread across three tools, or ten.

That fragmentation, more than the bug or the bad deploy, drives most of the time to recovery. The delay comes from humans rebuilding context that already existed somewhere in the stack, just not in one place.

A blueprint for fixing this combines three chosen technologies into an agentic AIOps platform. Neocarta automates ingestion into a Neo4j graph and exposes that graph to AI tools through MCP. LangGraph handles deterministic orchestration of the agent, and the Google Gen AI SDK with Gemini supplies the reasoning. There is no lakehouse, no batch ETL, no hand-built webhook plumbing and no dashboard of stale metrics. What remains is a live, queryable graph of operational reality that an agent can reach directly and act on.

A context graph instead of dashboards

Much AIOps tooling adds intelligence on top of an analytical data store designed for business intelligence rather than real-time reasoning, which is the wrong base. The design here frames operational state as a graph problem. Each service, deployment, incident, team and remediation action becomes a node, and the relationships between them, such as DEPLOYED_TO, ALERTS_ON, TRIGGERED_BY and OWNER_OF, are first-class edges.

Once infrastructure events form a connected graph and not isolated logs, root-cause analysis changes character. It stops being manual archaeology and becomes a graph traversal, a multi-hop query that an LLM can reason over directly through Graph RAG.

Four layers that close the loop

Four layers each carry one responsibility, and the loop closes because the agent's own actions are written back into the graph. The next incident it handles therefore knows what has already been tried.

  1. Automated collection with the Neocarta ingestion CLI. Instead of writing a webhook receiver for every tool, Neocarta connectors pull metadata and logs from ServiceNow, Dynatrace and GitHub and load them into the graph. No custom ingestion service has to be maintained per source.
  2. Systemic context in Neo4j, served by the Neocarta MCP server. This is the operational brain. Repositories, services, deployments, incidents, teams and remediation actions are nodes, and every relationship is a typed, directed edge. Neocarta puts an MCP server in front of the graph, so any MCP-aware tool, including the LangGraph agent, can query it without custom database code.
  3. Reasoning with LangGraph and Gemini. A LangGraph state machine runs an observe, orient, decide, act cycle. It fetches a contextual subgraph over MCP, compiles it into a prompt and gives Gemini a bounded choice: fix the problem or escalate to a human.
  4. Remediation and write-back. If the agent chooses to act, it launches the fix, for instance a GitHub Actions rollback, and then records the outcome in the graph as a new node. Most AIOps demos skip exactly this step, and it is what makes the loop closed and not one-shot.

Neocarta stands in for the custom ingestion service and the direct database driver calls. Its CLI automates collection into Neo4j, and its MCP server is what the agent actually talks to for graph context.

Layers 1 and 2: ingestion through Neocarta

The schema comes first, since it is the ontology the whole platform rests on. Node labels and relationship types are what each connector and each Cypher query ultimately read and write. Every entity is a typed node, and every dependency, ownership link and alert is a typed, directed edge. That structure is what turns an investigation across tools into a traversal.

Ingestion used to mean running a webhook receiver per source and hand-writing Cypher for each payload shape. Neocarta reduces that to configuration. Pointing its CLI at ServiceNow, Dynatrace and GitHub is enough for it to take care of schema discovery, incremental loads and mapping into the graph's node and relationship types.

# Register each source once — Neocarta handles auth, schema inference, and scheduling
neocarta connect servicenow --instance your-org.service-now.com --scope incident
neocarta connect dynatrace --tenant your-tenant-id --scope metrics,problems
neocarta connect github --org your-org --scope deployments,repos
# Point the loader at your graph and let it run
neocarta load --target neo4j://your-instance:7687 --database aiops-graph

Underneath, the same MERGE and CREATE patterns that one would write by hand are used. Persistent entities such as repositories, services and teams are merged, so duplicate nodes never appear. Events tied to a moment, such as a deployment or an incident, are inserted as new nodes each time, which keeps the timeline truthful and leaves earlier history intact. The difference is that nobody has to maintain the mapping code across three APIs that change payload shapes on their own schedules.

Beyond ingestion, Neocarta adds its MCP server. Once the graph is populated, it is exposed as an MCP endpoint, and a client that speaks MCP, LangGraph among them, can issue multi-hop graph queries as ordinary tool calls. The agent code needs no database driver layer to write or maintain.

# LangGraph's context-fetching node talks to Neocarta over MCP,
# not to a Neo4j driver directly
from langchain_mcp_adapters.client import MCPClient
neocarta_client = MCPClient(server_url="http://neocarta-mcp:8080")
tools = await neocarta_client.get_tools()   # graph_query, graph_traverse, etc.

Schema control stays in the engineers' hands. Constraints and indexes are defined as always, directly against Neo4j.

CREATE CONSTRAINT unique_service_id IF NOT EXISTS
FOR (s:Service) REQUIRE s.id IS UNIQUE;
CREATE INDEX incident_state_index IF NOT EXISTS
FOR (i:Incident) ON (i.state);

Layer 3: the LangGraph loop

This layer earns the word agentic. Instead of one LLM call, reasoning runs as a state machine with explicit nodes and edges, so each decision path can be inspected, tested and reasoned about, unlike the opaque loop of many agents.

The state is a typed dictionary that flows through the graph.

class AIOpsAgentState(TypedDict):
    incident_id: str
    failing_service_id: str
    graph_context_summary: Optional[str] = None
    safety_gate_passed: Optional[bool] = None
    chosen_action: Optional[str] = None
    target_repository: Optional[str] = None
    execution_result: Optional[str] = None

Four nodes drive the loop:

  • FetchGraphContext calls the graph_traverse tool in Neocarta's MCP server to follow an incident back to its service, then to the deployment that changed that service, the repository involved and the team that owns it. The agent code never holds a direct database connection.
  • VerifySafetyGate checks, through the same MCP query interface, whether a remediation already fired against this service during the past hour. This circuit breaker keeps an agent from cycling endlessly on a flapping service.
  • GeminiReasoning runs only if the gate passes. Gemini 2.5 Pro receives the summary of the graph context together with a narrow system instruction, and the only acceptable reply is one of two fixed tokens.
  • ExecuteRemediationTool launches the fix and writes a RemediationAction node back into Neo4j through Neocarta, which closes the loop.

If one component deserves the most attention, it is the safety gate. It is written here as an MCP tool call rather than a raw driver session.

async def evaluate_remediation_safety_gate(state: AIOpsAgentState) -> Dict:
    result = await neocarta_client.call_tool(
        "graph_query",
        cypher="""
            MATCH (s:Service {id: $service_id})<-[:EXECUTED_AGAINST]-(a:RemediationAction)
            WHERE a.timestamp >= datetime() - duration('PT1H')
            RETURN count(a) as active_actions
        """,
        params={"service_id": state["failing_service_id"]},
    )
    return {"safety_gate_passed": (result["active_actions"] == 0)}

An autonomous action loop without a safety gate should never ship. The gate is what stops an agent from retriggering a rollback every thirty seconds because the underlying anomaly has not yet cleared.

The Gemini node is boring on purpose, and that is its virtue.

system_instruction = (
    "You are an automated Agentic AIOps architect. "
    "Analyze the provided graph context summary. "
    "If a deployment recently modified the service state, "
    "output EXACTLY the word TRIGGER_ROLLBACK. "
    "If the context is clear but safe guidelines are violated, "
    "output HALT_ESCALATE. Do not append any other characters."
)

Limiting the model's output to two exact tokens makes the call behave more like a typed function than a chat reply, which is the right property when the result triggers a production rollback.

Layer 4: closing the loop

The last node either dispatches a rollback through the GitHub Actions API or does nothing except log the halt. Either way the graph is updated through the same MCP write path as everything else, so write-back needs no separate database credentials.

await neocarta_client.call_tool(
    "graph_write",
    cypher="""
        MATCH (s:Service {id: $service_id}), (i:Incident {id: $incident_id})
        CREATE (a:RemediationAction {id: $action_id, timestamp: datetime(), status: 'SUCCESS_ROLLBACK'})
        CREATE (a)-[:EXECUTED_AGAINST]->(s)
        CREATE (a)-[:ATTEMPTED_TO_FIX]->(i)
    """,
    params={"service_id": service_id, "incident_id": incident_id, "action_id": action_id},
)

What makes this a closed-loop system and not a throwaway automation script is a simple detail: the outcome of the agent's own action becomes context for the next incident it evaluates, so the platform grows more situationally aware over time instead of less.

An incident from start to finish

The blueprint includes a dry-run simulation that shows how the pieces cooperate, and a trace of it is instructive.

  1. 14:00:00. The payment-gateway service finishes a deployment, which adds a Deployment node connected to that service.
  2. 14:05:00. Dynatrace raises a critical anomaly on the same service.
  3. 14:06:00. A P1 incident appears in ServiceNow and becomes an Incident node, tied to the service by an ALERTS_ON edge.
  4. The LangGraph agent wakes on the new incident node. FetchGraphContext walks from Incident to Service to Deployment to Repository to Team and finds that the deployment happened five minutes before the anomaly.
  5. VerifySafetyGate finds no earlier remediation in the last hour, so the way is clear.
  6. Gemini receives the context summary, recognizes that the deployment caused the incident and returns TRIGGER_ROLLBACK.
  7. The tool executor starts the rollback workflow in GitHub Actions, then stores a fresh RemediationAction node in Neo4j.

From anomaly to remediation takes minutes, not a war-room call. Every step of the reasoning stays auditable in the graph afterward, so it is possible to query why the agent did what it did.

Once mapped, the incident forms a single connected structure spanning team, code, deployment, service, incident and the agent's own remediation. That is the exact subgraph Gemini reasons over, and one connected structure in place of three separate dashboards is what lets the agent follow a P1 incident back to the commit behind it.

The same story can be split into three moments that built the graph: the deploy, the crash and the fix. Each is a separate database write, yet together they form one continuous timeline running from a Git merge through a production alert to a closed-loop fix, in seven minutes.

Giving engineers a view of the graph

An autonomous system that engineers cannot inspect is a liability. The blueprint therefore includes a Streamlit dashboard built on pyvis that renders live graph topology and a running ledger of actions.

net = Network(height="600px", width="100%", bgcolor="#1a1c1e", directed=True)
color_map = {
    "Team": "#5f3dc4", "Repository": "#24292e", "Deployment": "#007acc",
    "Service": "#11a685", "Incident": "#c41c1c", "RemediationAction": "#cc0088"
}

Engineers get a pannable, zoomable view of the whole chain from Git merge to production incident to automated fix, with a sidebar ledger of every action the agent has taken, in order. Visibility is what makes an autonomous system trustworthy enough to switch on in production, because nothing it does is opaque.

A rollout that spares production

The recommended path is conservative, in three phases and not a single switch-on.

  • Week 1, ingestion and validation. Aim the Neocarta connectors at sandbox copies of each source, create the graph constraints and check that nodes map and link correctly before any production data is involved.
  • Week 2, staging dry runs. Build the LangGraph orchestrator on top of a read-only staging database and observe what Gemini decides, with execution switched off. Prompt or context problems surface here.
  • Week 3, closed-loop automation. Wire Layer 4 to actual staging API endpoints and verify that the write-back transactions complete the cycle, giving the safety gate real history to consult.

Only after all three phases hold up should remediation tools point at production endpoints.

Why the design holds

Several decisions separate a demo from something trustworthy in production.

  • State machines beat free-form agent loops. LangGraph makes every decision path explicit and inspectable, and no hidden reasoning chain exists that cannot be reproduced.
  • A safety gate precedes every LLM call that could trigger action. If a remediation fired recently, the agent cannot reach the reasoning node. That check lives in deterministic code, not in a prompt.
  • The output contract is constrained. Gemini is not asked for prose. Limiting it to one of two tokens makes its result as dependable as a function's return value.
  • The graph serves as memory and not only storage. Remediation actions are written into the same graph the agent reads, so its situational awareness accumulates and does not reset with each incident.
  • MCP is the seam between agent and graph. With Neocarta's MCP server sitting between LangGraph and Neo4j, the agent never holds a database driver or manages connection pools. It calls tools. The seam also allows swapping Gemini for another model, or LangGraph for another orchestrator, without touching ingestion or the graph layer.

Context that lives natively in a graph, a reasoning loop with fixed steps and a circuit breaker written into code together make autonomous remediation something that can ship, rather than something that only looks good on a slide. Sensible follow-ups would be shipping the stack with Docker Compose and sending Slack notifications from the Layer 4 router, so people hear about every autonomous action even when none of them had to intervene.