<?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: Rahul Reddy Maram</title>
    <description>The latest articles on DEV Community by Rahul Reddy Maram (@rahul_reddymaram_119a998).</description>
    <link>https://dev.to/rahul_reddymaram_119a998</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%2F4147256%2F9b2a97b5-2cec-4bf3-b790-a7c0982eb3e8.png</url>
      <title>DEV Community: Rahul Reddy Maram</title>
      <link>https://dev.to/rahul_reddymaram_119a998</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rahul_reddymaram_119a998"/>
    <language>en</language>
    <item>
      <title>I Removed an API Field. Hindsight Found Who Still Needed It.</title>
      <dc:creator>Rahul Reddy Maram</dc:creator>
      <pubDate>Mon, 28 Sep 2026 14:09:39 +0000</pubDate>
      <link>https://dev.to/rahul_reddymaram_119a998/i-removed-an-api-field-hindsight-found-who-still-needed-it-3c8i</link>
      <guid>https://dev.to/rahul_reddymaram_119a998/i-removed-an-api-field-hindsight-found-who-still-needed-it-3c8i</guid>
      <description>&lt;p&gt;API compatibility problems are rarely difficult because removing a field is technically hard. The difficult part is knowing who still depends on that field.&lt;br&gt;
I started with a simple question: if I remove phone from a Customer API response, how can an agent tell me whether some downstream consumer still needs it?&lt;br&gt;
That question led me to build an API Compatibility Agent around Spring Boot, Flask, Hindsight, and Groq. The interesting part was not teaching the system that removing a field can be breaking. That is easy. The interesting part was giving it memory so it could connect a proposed change to a dependency recorded earlier.&lt;/p&gt;

&lt;p&gt;The result is a system with a clear separation of responsibilities: Java manages the application and compatibility rules, Python runs the memory-backed analysis, Hindsight stores relationships and previous analyses, and the language model turns retrieved evidence into an explanation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Problem:&lt;/strong&gt; &lt;em&gt;An API Change Is Smaller Than Its Impact&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Consider a Customer API:&lt;/p&gt;

&lt;p&gt;GET /api/customers/{id}&lt;/p&gt;

&lt;p&gt;with a response such as:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;{&lt;br&gt;
  "id": 1042,&lt;br&gt;
  "name": "Rahul",&lt;br&gt;
  "email": "rahul@example.com",&lt;br&gt;
  "phone": "+91..."&lt;br&gt;
}&lt;br&gt;
&lt;/code&gt;&lt;br&gt;
Now imagine I decide that phone is no longer needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The code change might be tiny:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Remove phone from the Customer API response&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But the real question is:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Who is still reading phone?&lt;/p&gt;

&lt;p&gt;A web client might display it. A billing service might use it. A notification workflow might depend on it. A client released months ago might still expect it.&lt;/p&gt;

&lt;p&gt;A basic compatibility rule can tell me that removing a field may break consumers. It cannot, by itself, tell me which known consumer is connected to that field.&lt;/p&gt;

&lt;p&gt;So I separated the problem into two layers:&lt;/p&gt;

&lt;p&gt;Deterministic compatibility: What kind of API change is this?&lt;/p&gt;

&lt;p&gt;Remembered context: What consumers have previously been associated with the changed field?&lt;/p&gt;

&lt;p&gt;The first belongs in application logic.&lt;/p&gt;

&lt;p&gt;The second is where persistent memory becomes useful.&lt;/p&gt;

&lt;p&gt;The Architecture I Built&lt;/p&gt;

&lt;p&gt;The project has two application layers.&lt;/p&gt;

&lt;p&gt;The Java side is a Spring Boot application backed by MySQL. It models API endpoints and API changes and exposes REST endpoints for compatibility operations.&lt;/p&gt;

&lt;p&gt;The Python side is a separate Flask service. It exposes /analyze for compatibility analysis and /remember for recording an API-consumer dependency.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The high-level flow is:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                Developer
                   |
                   v
          +------------------+
          |   Spring Boot    |
          | Application/API  |
          +--------+---------+
                   |
                   | /analyze
                   v
          +------------------+
          |  Python / Flask  |
          |   Agent Layer    |
          +--------+---------+
                   |
         +---------+---------+
         |                   |
         v                   v
  +-------------+      +-----------+
  |  Hindsight  |      |   Groq    |
  |   Memory    |      | Explanation|
  +-------------+      +-----------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;I intentionally kept these boundaries visible.&lt;/p&gt;

&lt;p&gt;Spring Boot is not trying to become the memory system. Hindsight is not deciding compatibility by itself. The language model is not treated as the source of truth for consumer dependencies.&lt;/p&gt;

&lt;p&gt;Each component has a narrower responsibility.&lt;/p&gt;

&lt;p&gt;Remembering the Dependency&lt;/p&gt;

&lt;p&gt;Suppose I know that the Billing Service depends on phone:&lt;/p&gt;

&lt;p&gt;API: Customer API&lt;br&gt;
Consumer: Billing Service&lt;br&gt;
Dependency: Billing Service depends on the phone field.&lt;/p&gt;

&lt;p&gt;The Python service exposes a /remember endpoint for this. The core implementation is straightforward:&lt;/p&gt;

&lt;p&gt;def remember_dependency(api_name, consumer, field):&lt;br&gt;
    client = create_hindsight_client()&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;try:
    client.retain(
        bank_id=BANK_ID,
        content=(
            f"API: {api_name}. "
            f"Consumer: {consumer}. "
            f"Dependency: {consumer} depends on the {field} field."
        ),
        context="api-consumer-dependency"
    )
finally:
    client.close()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;I like this representation because it is deliberately concrete: an API, a consumer, a field, and a dependency between them.&lt;/p&gt;

&lt;p&gt;This is where Hindsight GitHub becomes important. Instead of passing every dependency into every analysis request, the relationship can persist in memory and become available when a related API change appears later.&lt;/p&gt;

&lt;p&gt;That is the difference between giving an agent context for one request and giving it context that survives across requests.&lt;/p&gt;

&lt;p&gt;What Happens When I Remove a Field?&lt;/p&gt;

&lt;p&gt;The analysis endpoint accepts a natural-language change:&lt;/p&gt;

&lt;p&gt;Remove phone from the Customer API response&lt;/p&gt;

&lt;p&gt;The agent first extracts the field name:&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 this example, the extracted field is phone.&lt;/p&gt;

&lt;p&gt;The agent then asks Hindsight for consumers associated with that field:&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;This is the point where the project moves beyond a static rule.&lt;/p&gt;

&lt;p&gt;The system is no longer asking an LLM to guess whether an API change might be dangerous.&lt;/p&gt;

&lt;p&gt;It first asks the memory layer:&lt;/p&gt;

&lt;p&gt;What do I already know about this field?&lt;/p&gt;

&lt;p&gt;I Did Not Want the LLM Inventing Consumers&lt;/p&gt;

&lt;p&gt;This became one of the most important design decisions.&lt;/p&gt;

&lt;p&gt;A language model can produce a convincing answer even when the underlying dependency does not exist. I did not want the model deciding which consumers existed.&lt;/p&gt;

&lt;p&gt;After Hindsight returns memories, the agent filters them for the requested field and dependency relationship:&lt;/p&gt;

&lt;p&gt;matching_memories = []&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if (
    field.lower() in text.lower()
    and "depends on" in text.lower()
):
    matching_memories.append(text)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Those memories become evidence for the Groq request.&lt;/p&gt;

&lt;p&gt;The prompt explicitly tells the model:&lt;/p&gt;

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

&lt;p&gt;That gives the system a useful separation:&lt;/p&gt;

&lt;p&gt;Component&lt;/p&gt;

&lt;p&gt;Responsibility&lt;/p&gt;

&lt;p&gt;Spring Boot&lt;/p&gt;

&lt;p&gt;Application API, persistence, and compatibility operations&lt;/p&gt;

&lt;p&gt;Python agent&lt;/p&gt;

&lt;p&gt;Parses the proposed change and coordinates analysis&lt;/p&gt;

&lt;p&gt;Hindsight&lt;/p&gt;

&lt;p&gt;Stores and retrieves dependency evidence&lt;/p&gt;

&lt;p&gt;Compatibility logic&lt;/p&gt;

&lt;p&gt;Applies deterministic compatibility rules&lt;/p&gt;

&lt;p&gt;Groq&lt;/p&gt;

&lt;p&gt;Explains the result using retrieved evidence&lt;/p&gt;

&lt;p&gt;I find this easier to reason about than an architecture where the model discovers, judges, and explains everything.&lt;/p&gt;

&lt;p&gt;The Same Change Can Produce Different Results&lt;/p&gt;

&lt;p&gt;Suppose Hindsight contains:&lt;/p&gt;

&lt;p&gt;API: Customer API&lt;br&gt;
Consumer: Billing Service&lt;br&gt;
Dependency: Billing Service depends on the phone field.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I submit:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Remove phone from Customer API&lt;/p&gt;

&lt;p&gt;The agent extracts phone, retrieves the dependency, finds a matching consumer, and classifies the change as:&lt;/p&gt;

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

&lt;p&gt;The remembered dependency is then included in the prompt sent to Groq, allowing the explanation to connect the proposed change to a known consumer.&lt;/p&gt;

&lt;p&gt;If Hindsight has no matching dependency, the agent returns:&lt;/p&gt;

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

&lt;p&gt;I deliberately chose those words.&lt;/p&gt;

&lt;p&gt;I did not want the system to say SAFE, because "I don't know of a dependency" is not the same as "no dependency exists."&lt;/p&gt;

&lt;p&gt;Persistent memory can tell me what the system has recorded. It cannot prove that an unrecorded consumer does not exist.&lt;/p&gt;

&lt;p&gt;Keeping Deterministic Rules in Java&lt;/p&gt;

&lt;p&gt;The memory-backed agent is not the only compatibility mechanism.&lt;/p&gt;

&lt;p&gt;The Java CompatibilityService handles basic change categories directly:&lt;/p&gt;

&lt;p&gt;if ("FIELD_ADDED".equalsIgnoreCase(changeType)) {&lt;br&gt;
    return new CompatibilityResult(&lt;br&gt;
            "COMPATIBLE",&lt;br&gt;
            "Adding a new field is generally backward compatible."&lt;br&gt;
    );&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;if ("FIELD_REMOVED".equalsIgnoreCase(changeType)) {&lt;br&gt;
    return new CompatibilityResult(&lt;br&gt;
            "POTENTIALLY_BREAKING",&lt;br&gt;
            "Removing a field may break existing API consumers."&lt;br&gt;
    );&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;I do not need an LLM to tell me that removing an existing response field may affect clients.&lt;/p&gt;

&lt;p&gt;The interesting question is what comes next:&lt;/p&gt;

&lt;p&gt;Do I have remembered evidence about a consumer that actually depends on this field?&lt;/p&gt;

&lt;p&gt;The two layers therefore complement each other:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   ** Static Compatibility Rule
              |
              v
      POTENTIALLY BREAKING
              |
              v
        Hindsight Lookup
           /       \
          /         \
         v           v
Known Consumer    No Known Consumer
      |                  |
      v                  v
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Explain Impact      NO_KNOWN_IMPACT**&lt;/p&gt;

&lt;p&gt;Spring Boot and Python Have a Clean Boundary&lt;/p&gt;

&lt;p&gt;The Java application communicates with the Python service through RestClient:&lt;/p&gt;

&lt;p&gt;public String analyzeChange(String change) {&lt;/p&gt;

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

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

&lt;/div&gt;

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

&lt;p&gt;This keeps the application boundary simple.&lt;/p&gt;

&lt;p&gt;Spring Boot does not need to embed the Hindsight client, while the Python agent can evolve its memory and model workflow independently.&lt;/p&gt;

&lt;p&gt;The flow is easy to trace:&lt;/p&gt;

&lt;p&gt;Spring Boot&lt;br&gt;
    |&lt;br&gt;
    | HTTP&lt;br&gt;
    v&lt;br&gt;
Flask Agent&lt;br&gt;
    |&lt;br&gt;
    +---- Hindsight&lt;br&gt;
    |&lt;br&gt;
    +---- Groq&lt;/p&gt;

&lt;p&gt;For a system with several external dependencies, explicit boundaries make debugging easier.&lt;/p&gt;

&lt;p&gt;Hindsight Stores More Than the Dependency&lt;/p&gt;

&lt;p&gt;The most interesting part of the design is what happens after the first analysis.&lt;/p&gt;

&lt;p&gt;The agent also stores the compatibility result in Hindsight:&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;So the memory layer can contain both:&lt;/p&gt;

&lt;p&gt;api-consumer-dependency&lt;br&gt;
    └── Billing Service depends on phone&lt;/p&gt;

&lt;p&gt;api-compatibility-analysis&lt;br&gt;
    └── Removing phone was potentially breaking&lt;/p&gt;

&lt;p&gt;That changes how I think about the agent.&lt;/p&gt;

&lt;p&gt;A conventional utility processes a request and returns an answer.&lt;/p&gt;

&lt;p&gt;A memory-backed agent can accumulate context that remains useful for later requests.&lt;/p&gt;

&lt;p&gt;The Hindsight documentation describes the underlying memory capabilities. In this project, I use those capabilities specifically for API-consumer relationships and compatibility analysis.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I Learned&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. API compatibility is partly a knowledge problem&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The syntax of an API change is easy to inspect.&lt;/p&gt;

&lt;p&gt;The difficult part is understanding the ecosystem around that API.&lt;/p&gt;

&lt;p&gt;Removing a field may require only a small code change, but its impact depends on the consumers behind the contract.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Persistent memory fits slowly changing relationships&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Consumer dependencies are a natural fit for persistent memory. They do not need to be rediscovered every time someone proposes a change.&lt;/p&gt;

&lt;p&gt;Record the relationship once and retrieve it when the related field appears in a future analysis.&lt;br&gt;
**&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Evidence and explanation should be separate**&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I deliberately let Hindsight provide remembered evidence and let the language model explain that evidence.&lt;/p&gt;

&lt;p&gt;That makes the system easier to inspect and reduces the temptation to treat a generated explanation as authoritative dependency data.&lt;br&gt;
**&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;"No known impact" is not "safe"**&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If no matching memory is found, the agent knows only that it has no recorded dependency matching the query.&lt;/p&gt;

&lt;p&gt;That should not become an absolute safety guarantee.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Not everything needs an LLM&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The Java compatibility service can handle straightforward compatibility categories deterministically.&lt;/p&gt;

&lt;p&gt;The model becomes useful when there is contextual information to explain.&lt;/p&gt;

&lt;p&gt;That keeps the system more predictable and makes the role of the LLM clearer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where I Would Take It Next&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The current implementation gives me a foundation for a more complete compatibility platform.&lt;/p&gt;

&lt;p&gt;The biggest improvement would be reducing manual dependency recording. Today, /remember explicitly accepts:&lt;/p&gt;

&lt;p&gt;api_name&lt;br&gt;
consumer&lt;br&gt;
field&lt;/p&gt;

&lt;p&gt;A production version could populate this memory automatically from API specifications, service repositories, client code, gateway information, or deployment metadata.&lt;/p&gt;

&lt;p&gt;I would also make change detection more robust. The current implementation focuses on phrases such as Remove phone; a broader engine should understand:&lt;/p&gt;

&lt;p&gt;field removals&lt;/p&gt;

&lt;p&gt;field renames&lt;/p&gt;

&lt;p&gt;field type changes&lt;/p&gt;

&lt;p&gt;endpoint removals&lt;/p&gt;

&lt;p&gt;response-shape changes&lt;/p&gt;

&lt;p&gt;authentication changes&lt;/p&gt;

&lt;p&gt;required-field changes&lt;/p&gt;

&lt;p&gt;Another useful extension would be API-version awareness. Removing a field from an existing /v1 contract has a different operational meaning from making the same change while introducing /v2.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The principle would remain the same:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Identify what changed, retrieve what is known about its consumers, and explain the impact using evidence rather than speculation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Final Thought&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The part of this project that stayed with me was how small an API change can look in a diff.&lt;/p&gt;

&lt;p&gt;Remove one field.&lt;/p&gt;

&lt;p&gt;Delete one property.&lt;/p&gt;

&lt;p&gt;Clean up one response.&lt;/p&gt;

&lt;p&gt;The code change can take minutes. Understanding who depends on that field can take much longer.&lt;/p&gt;

&lt;p&gt;That is the gap I wanted to address with memory.&lt;/p&gt;

&lt;p&gt;I did not build the agent simply to tell me that API changes can be dangerous.&lt;/p&gt;

&lt;p&gt;I built it to answer a more useful question:&lt;/p&gt;

&lt;p&gt;*&lt;em&gt;Who do I already know depends on this change, and what evidence do I have?&lt;br&gt;
*&lt;/em&gt;&lt;br&gt;
With Hindsight behind that question, the agent can carry knowledge from one analysis into the next instead of starting from zero every time.&lt;/p&gt;

&lt;p&gt;And that is the part of API compatibility I found most interesting: the difficult problem is not detecting that a contract changed. It is remembering who was relying on it when it did.&lt;/p&gt;

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