<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Aravind Dharavath</title>
    <description>The latest articles on DEV Community by Aravind Dharavath (@aravind_dharavath_76224f7).</description>
    <link>https://dev.to/aravind_dharavath_76224f7</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4149263%2F45ba5992-af7e-4812-8ffb-8de816d69bc9.png</url>
      <title>DEV Community: Aravind Dharavath</title>
      <link>https://dev.to/aravind_dharavath_76224f7</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aravind_dharavath_76224f7"/>
    <language>en</language>
    <item>
      <title>How Hindsight Turned a Field Removal Into Evidence</title>
      <dc:creator>Aravind Dharavath</dc:creator>
      <pubDate>Tue, 29 Sep 2026 10:34:43 +0000</pubDate>
      <link>https://dev.to/aravind_dharavath_76224f7/how-hindsight-turned-a-field-removal-into-evidence-2230</link>
      <guid>https://dev.to/aravind_dharavath_76224f7/how-hindsight-turned-a-field-removal-into-evidence-2230</guid>
      <description>&lt;p&gt;The dangerous part of removing an API field isn't the diff. It's knowing who still depends on it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fotbftrl2ves913qxodza.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fotbftrl2ves913qxodza.png" alt=" " width="800" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I built API Sentinel around that problem: instead of treating an API change as an isolated text comparison, I wanted the compatibility check to remember what consumer applications had told us before. Hindsight became the memory layer that made that possible.&lt;/p&gt;

&lt;p&gt;The problem I wanted to solve&lt;/p&gt;

&lt;p&gt;Consider a Course API with fields such as:&lt;/p&gt;

&lt;p&gt;Course&lt;br&gt;
├── id&lt;br&gt;
├── title&lt;br&gt;
├── description&lt;br&gt;
├── category&lt;br&gt;
└── instructor&lt;/p&gt;

&lt;p&gt;At some point, someone proposes:&lt;/p&gt;

&lt;p&gt;Remove description from Course API&lt;/p&gt;

&lt;p&gt;From the provider's point of view, this can look like a small cleanup. From a consumer's point of view, it can be a breaking change.&lt;/p&gt;

&lt;p&gt;The problem is that an API compatibility tool usually sees the proposed change, the API contract, and perhaps a source-code diff. It does not automatically know that an E-Learning application has been using description for months.&lt;/p&gt;

&lt;p&gt;That missing context is what I wanted to capture.&lt;/p&gt;

&lt;p&gt;API Sentinel is a Spring Boot application backed by MySQL, with a Python/Flask agent handling the memory and LLM workflow. The Python agent talks to Hindsight GitHub for persistent memory and to Groq for the final compatibility explanation.&lt;/p&gt;

&lt;p&gt;The important architectural decision was to separate the system into two kinds of state:&lt;/p&gt;

&lt;p&gt;MySQL stores API endpoints and proposed changes.&lt;/p&gt;

&lt;p&gt;Hindsight stores knowledge about consumers and previous compatibility analyses.&lt;/p&gt;

&lt;p&gt;That distinction matters because an API dependency is not just another database row. It is context that should be retrievable when a later change makes it relevant.&lt;/p&gt;

&lt;p&gt;The flow is deliberately simple&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frqgcxsfo0w716gjozzce.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frqgcxsfo0w716gjozzce.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The system has four important steps.&lt;/p&gt;

&lt;p&gt;First, a consumer registers a dependency:&lt;/p&gt;

&lt;p&gt;POST /api/ai/remember&lt;/p&gt;

&lt;p&gt;with:&lt;/p&gt;

&lt;p&gt;{&lt;br&gt;
  "application": "E-Learning App",&lt;br&gt;
  "api": "Course API",&lt;br&gt;
  "field": "description"&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Spring Boot forwards that request to the Python agent.&lt;/p&gt;

&lt;p&gt;The agent stores the relationship in Hindsight under the api-consumer-dependency context.&lt;/p&gt;

&lt;p&gt;Later, a developer submits:&lt;/p&gt;

&lt;p&gt;POST /api/ai/analyze&lt;/p&gt;

&lt;p&gt;with:&lt;/p&gt;

&lt;p&gt;{&lt;br&gt;
  "change": "Remove description from Course API"&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;The agent extracts the field name, asks Hindsight which consumers depend on it, filters the returned memories for direct dependency statements, and only then asks the LLM to explain the impact.&lt;/p&gt;

&lt;p&gt;Finally, the compatibility analysis itself is retained in Hindsight.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcxuwmrr3k62wsqrghzps.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fcxuwmrr3k62wsqrghzps.png" alt=" " width="800" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So the system is not just:&lt;/p&gt;

&lt;p&gt;API change -&amp;gt; LLM -&amp;gt; answer&lt;/p&gt;

&lt;p&gt;It is:&lt;/p&gt;

&lt;p&gt;API change&lt;br&gt;
    |&lt;br&gt;
    v&lt;br&gt;
identify changed field&lt;br&gt;
    |&lt;br&gt;
    v&lt;br&gt;
recall historical dependencies&lt;br&gt;
    |&lt;br&gt;
    v&lt;br&gt;
filter relevant memories&lt;br&gt;
    |&lt;br&gt;
    v&lt;br&gt;
build compatibility context&lt;br&gt;
    |&lt;br&gt;
    v&lt;br&gt;
LLM analysis&lt;br&gt;
    |&lt;br&gt;
    v&lt;br&gt;
remember the analysis&lt;/p&gt;

&lt;p&gt;That middle section is the part I cared about most.&lt;/p&gt;

&lt;p&gt;Why I didn't send the whole memory store to the LLM&lt;/p&gt;

&lt;p&gt;One tempting implementation would be to retrieve a large amount of historical memory and put everything into the prompt.&lt;/p&gt;

&lt;p&gt;I avoided that.&lt;/p&gt;

&lt;p&gt;The agent first identifies the field being removed:&lt;/p&gt;

&lt;p&gt;match = re.search(&lt;br&gt;
    r"remove\s+([A-Za-z0-9_]+)",&lt;br&gt;
    change,&lt;br&gt;
    re.IGNORECASE&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;if not match:&lt;br&gt;
    return (&lt;br&gt;
        "UNKNOWN",&lt;br&gt;
        "Could not identify the API field being removed."&lt;br&gt;
    )&lt;/p&gt;

&lt;p&gt;field = match.group(1)&lt;/p&gt;

&lt;p&gt;For a request like:&lt;/p&gt;

&lt;p&gt;Remove description from Course API&lt;/p&gt;

&lt;p&gt;the field becomes:&lt;/p&gt;

&lt;p&gt;description&lt;/p&gt;

&lt;p&gt;That gives the memory query a concrete target.&lt;/p&gt;

&lt;p&gt;The agent then asks Hindsight:&lt;/p&gt;

&lt;p&gt;result = hindsight_client.recall(&lt;br&gt;
    bank_id=BANK_ID,&lt;br&gt;
    query=(&lt;br&gt;
        f"Which API consumers depend on the {field} field? "&lt;br&gt;
        f"Return only direct consumer dependencies."&lt;br&gt;
    )&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;I like this boundary because the LLM isn't being asked to discover the dependency from an arbitrary pile of text. The retrieval step is responsible for finding historical evidence; the LLM is responsible for explaining what that evidence means.&lt;/p&gt;

&lt;p&gt;That is a useful separation of concerns.&lt;/p&gt;

&lt;p&gt;The memory itself is intentionally boring&lt;/p&gt;

&lt;p&gt;The dependency stored in Hindsight is simple:&lt;/p&gt;

&lt;p&gt;client.retain(&lt;br&gt;
    bank_id=BANK_ID,&lt;br&gt;
    content=(&lt;br&gt;
        f"API: {api_name}. "&lt;br&gt;
        f"Consumer: {consumer}. "&lt;br&gt;
        f"Dependency: {consumer} depends on the {field} field."&lt;br&gt;
    ),&lt;br&gt;
    context="api-consumer-dependency"&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;There is no elaborate graph schema hiding behind it.&lt;/p&gt;

&lt;p&gt;For the E-Learning example, the memory effectively says:&lt;/p&gt;

&lt;p&gt;API: Course API.&lt;br&gt;
Consumer: E-Learning App.&lt;br&gt;
Dependency: E-Learning App depends on the description field.&lt;/p&gt;

&lt;p&gt;That simplicity turned out to be useful. The memory has a clear statement that can be retrieved and inspected, while the application-specific reasoning remains outside the storage layer.&lt;/p&gt;

&lt;p&gt;The Hindsight documentation describes persistent agent memory as a way to retain information and retrieve it later. In API Sentinel, I use that capability for something fairly concrete: preserving the relationship between an API field and a consumer application across separate interactions.&lt;/p&gt;

&lt;p&gt;The interesting part happens when the field disappears&lt;/p&gt;

&lt;p&gt;Once the memory comes back, I don't immediately trust every result.&lt;/p&gt;

&lt;p&gt;The agent filters the recalled memories:&lt;/p&gt;

&lt;p&gt;for memory in result.results:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;text = memory.text.lower()
field_lower = field.lower()

if (
    field_lower in text
    and (
        "depends on" in text
        or "dependency on" in text
        or "relies on" in text
        or "rely on" in text
    )
):
    matching_memories.append(memory.text)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This is intentionally conservative.&lt;/p&gt;

&lt;p&gt;If the recalled memory does not mention the field and a direct dependency relationship, it is not treated as evidence of an affected consumer.&lt;/p&gt;

&lt;p&gt;The matching memories are then deduplicated:&lt;/p&gt;

&lt;p&gt;matching_memories = list(dict.fromkeys(matching_memories))&lt;/p&gt;

&lt;p&gt;If at least one dependency is found, the compatibility status becomes:&lt;/p&gt;

&lt;p&gt;POTENTIALLY_BREAKING&lt;/p&gt;

&lt;p&gt;Otherwise:&lt;/p&gt;

&lt;p&gt;NO_KNOWN_IMPACT&lt;/p&gt;

&lt;p&gt;That distinction is important. "No known impact" is not the same thing as "safe." It means the memory layer did not contain a known dependency matching the field.&lt;/p&gt;

&lt;p&gt;I wanted that uncertainty to remain visible instead of turning absence of evidence into a confident compatibility claim.&lt;/p&gt;

&lt;p&gt;The LLM gets evidence, not authority&lt;/p&gt;

&lt;p&gt;The final prompt gives the model the proposed change, the field, the compatibility status, and the relevant historical memories:&lt;/p&gt;

&lt;p&gt;prompt = f"""&lt;br&gt;
You are an API compatibility analysis assistant.&lt;/p&gt;

&lt;p&gt;Proposed API change:&lt;br&gt;
{change}&lt;/p&gt;

&lt;p&gt;Field being changed:&lt;br&gt;
{field}&lt;/p&gt;

&lt;p&gt;Compatibility status:&lt;br&gt;
{status}&lt;/p&gt;

&lt;p&gt;Relevant historical memories from Hindsight:&lt;br&gt;
{memory_text}&lt;/p&gt;

&lt;p&gt;Provide a concise developer-focused explanation.&lt;/p&gt;

&lt;p&gt;Include:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Compatibility assessment&lt;/li&gt;
&lt;li&gt;Affected consumers&lt;/li&gt;
&lt;li&gt;Why the change may cause problems&lt;/li&gt;
&lt;li&gt;A recommended migration action&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Do not invent consumers or dependencies that are not present in the Hindsight memories.&lt;br&gt;
"""&lt;/p&gt;

&lt;p&gt;That last instruction is one of the most important lines in the system.&lt;/p&gt;

&lt;p&gt;The model should explain the evidence, not manufacture more of it.&lt;/p&gt;

&lt;p&gt;For the example, the useful chain is:&lt;/p&gt;

&lt;p&gt;Proposed change:&lt;br&gt;
Remove description from Course API&lt;/p&gt;

&lt;p&gt;Historical evidence:&lt;br&gt;
E-Learning App depends on description&lt;/p&gt;

&lt;p&gt;Compatibility result:&lt;br&gt;
POTENTIALLY_BREAKING&lt;/p&gt;

&lt;p&gt;The LLM can turn that into a developer-readable explanation and migration guidance, but the affected consumer comes from stored evidence.&lt;/p&gt;

&lt;p&gt;This is also why I chose a relatively low temperature:&lt;/p&gt;

&lt;p&gt;temperature=0.2&lt;/p&gt;

&lt;p&gt;The goal isn't creative generation. It's a consistent explanation of a compatibility finding.&lt;/p&gt;

&lt;p&gt;I also made the analysis itself part of memory&lt;/p&gt;

&lt;p&gt;After the LLM responds, API Sentinel stores the result:&lt;/p&gt;

&lt;p&gt;hindsight_client.retain(&lt;br&gt;
    bank_id=BANK_ID,&lt;br&gt;
    content=(&lt;br&gt;
        f"API compatibility analysis: "&lt;br&gt;
        f"Proposed change: {change}. "&lt;br&gt;
        f"Field: {field}. "&lt;br&gt;
        f"Result: {status}. "&lt;br&gt;
        f"AI analysis: {ai_reason}"&lt;br&gt;
    ),&lt;br&gt;
    context="api-compatibility-analysis"&lt;br&gt;
)&lt;/p&gt;

&lt;p&gt;This gives the system a second kind of historical knowledge.&lt;/p&gt;

&lt;p&gt;The first kind is:&lt;/p&gt;

&lt;p&gt;E-Learning App depends on description&lt;/p&gt;

&lt;p&gt;The second is:&lt;/p&gt;

&lt;p&gt;A previous compatibility analysis concluded that&lt;br&gt;
removing description was potentially breaking.&lt;/p&gt;

&lt;p&gt;That means compatibility work doesn't have to disappear after the HTTP response is returned.&lt;/p&gt;

&lt;p&gt;The broader idea of agent memory is useful here because the agent's context can extend beyond one request. For API compatibility, that maps naturally to historical relationships and decisions.&lt;/p&gt;

&lt;p&gt;How the Java and Python pieces fit together&lt;/p&gt;

&lt;p&gt;I kept the Spring Boot layer responsible for application-facing APIs and persistence.&lt;/p&gt;

&lt;p&gt;The controller exposes the memory and analysis endpoints:&lt;/p&gt;

&lt;p&gt;@PostMapping("/analyze")&lt;br&gt;
public String analyze(@RequestBody Map request) {&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;String change = request.get("change");

return pythonAgentService.analyzeChange(change);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;@PostMapping("/remember")&lt;br&gt;
public String remember(@RequestBody Map request) {&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;String application = request.get("application");
String api = request.get("api");
String field = request.get("field");

return pythonAgentService.rememberDependency(
        application,
        api,
        field
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;}&lt;/p&gt;

&lt;p&gt;The Java service then forwards those calls to the Flask agent on port 5000:&lt;/p&gt;

&lt;p&gt;return restClient.post()&lt;br&gt;
        .uri("/analyze")&lt;br&gt;
        .body(request)&lt;br&gt;
        .retrieve()&lt;br&gt;
        .body(String.class);&lt;/p&gt;

&lt;p&gt;That split gives me a clean boundary:&lt;/p&gt;

&lt;p&gt;Browser&lt;br&gt;
   |&lt;br&gt;
   v&lt;br&gt;
Spring Boot :8080&lt;br&gt;
   |&lt;br&gt;
   v&lt;br&gt;
Python Agent :5000&lt;br&gt;
   |&lt;br&gt;
   +--&amp;gt; Hindsight&lt;br&gt;
   |&lt;br&gt;
   +--&amp;gt; Groq&lt;/p&gt;

&lt;p&gt;The API endpoint and change entities remain normal JPA models, while the agent-specific memory workflow stays in Python.&lt;/p&gt;

&lt;p&gt;What the developer actually sees&lt;/p&gt;

&lt;p&gt;From the UI, the interaction is intentionally small.&lt;/p&gt;

&lt;p&gt;The developer enters:&lt;/p&gt;

&lt;p&gt;Remove description from Course API&lt;/p&gt;

&lt;p&gt;and clicks:&lt;/p&gt;

&lt;p&gt;Analyze Compatibility&lt;/p&gt;

&lt;p&gt;The browser calls:&lt;/p&gt;

&lt;p&gt;const response = await fetch("/api/ai/analyze", {&lt;br&gt;
    method: "POST",&lt;br&gt;
    headers: {&lt;br&gt;
        "Content-Type": "application/json"&lt;br&gt;
    },&lt;br&gt;
    body: JSON.stringify({&lt;br&gt;
        change: change&lt;br&gt;
    })&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;The result exposes two pieces of information:&lt;/p&gt;

&lt;p&gt;Compatibility Status&lt;br&gt;
POTENTIALLY_BREAKING&lt;/p&gt;

&lt;p&gt;and an AI-generated explanation.&lt;/p&gt;

&lt;p&gt;The UI also explicitly identifies Hindsight as the memory source. I wanted that to be visible because the important difference between a stateless LLM call and this workflow is where the context came from.&lt;/p&gt;

&lt;p&gt;What I learned&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;API compatibility is partly a history problem&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A schema diff can tell me that a field disappeared.&lt;/p&gt;

&lt;p&gt;It cannot, by itself, tell me which internal application still depends on that field.&lt;/p&gt;

&lt;p&gt;Once I started thinking about compatibility as historical knowledge rather than only structural comparison, persistent memory became a natural part of the architecture.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Retrieval should happen before reasoning&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I don't want the model guessing which applications might be affected.&lt;/p&gt;

&lt;p&gt;I want the system to retrieve known dependencies first and then give the model those dependencies as evidence.&lt;/p&gt;

&lt;p&gt;That makes the LLM's role narrower and easier to reason about.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;"No known impact" needs careful wording&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If Hindsight returns no matching dependency, API Sentinel reports NO_KNOWN_IMPACT.&lt;/p&gt;

&lt;p&gt;It does not report "definitely safe."&lt;/p&gt;

&lt;p&gt;That distinction matters in production because a memory system can only know what has been registered or otherwise captured. An unknown consumer remains possible.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Persistent memory is useful when the remembered fact has a clear lifecycle&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;"Remember everything" isn't a useful architecture.&lt;/p&gt;

&lt;p&gt;In this system, I have two explicit memory contexts:&lt;/p&gt;

&lt;p&gt;api-consumer-dependency&lt;br&gt;
api-compatibility-analysis&lt;/p&gt;

&lt;p&gt;That gives the stored information a purpose and makes retrieval easier to reason about.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The next hard problem is discovering dependencies automatically&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The current architecture expects a consumer to register its dependency explicitly.&lt;/p&gt;

&lt;p&gt;That's useful, but it leaves an obvious next step: automatically discovering dependencies from OpenAPI specifications, API gateway logs, runtime instrumentation, static analysis, or CI pipelines.&lt;/p&gt;

&lt;p&gt;That is where I would take the system next.&lt;/p&gt;

&lt;p&gt;The part I would keep&lt;/p&gt;

&lt;p&gt;The most important design decision in API Sentinel isn't the LLM call.&lt;/p&gt;

&lt;p&gt;It's the decision to preserve the relationship:&lt;/p&gt;

&lt;p&gt;Consumer&lt;br&gt;
   |&lt;br&gt;
   | depends on&lt;br&gt;
   v&lt;br&gt;
API field&lt;/p&gt;

&lt;p&gt;and make that relationship available when the field eventually changes.&lt;/p&gt;

&lt;p&gt;Removing description from the Course API is still just one line of an API diff.&lt;/p&gt;

&lt;p&gt;With historical memory, that line can be connected to evidence about the applications that use it.&lt;/p&gt;

&lt;p&gt;That's the difference I was trying to build: not an assistant that merely explains an API diff, but an analysis flow that can remember why a seemingly small change matters.&lt;/p&gt;

</description>
      <category>api</category>
      <category>python</category>
      <category>ai</category>
      <category>java</category>
    </item>
  </channel>
</rss>
