<?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: Divyadharshini V</title>
    <description>The latest articles on DEV Community by Divyadharshini V (@divya24).</description>
    <link>https://dev.to/divya24</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%2F4231787%2F4dbfb3f3-9082-49e2-926c-8ac3829978fe.png</url>
      <title>DEV Community: Divyadharshini V</title>
      <link>https://dev.to/divya24</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/divya24"/>
    <language>en</language>
    <item>
      <title>I Added Hindsight Memory to an ML Agent. F1 Didn't Move.</title>
      <dc:creator>Divyadharshini V</dc:creator>
      <pubDate>Sat, 07 Nov 2026 10:38:24 +0000</pubDate>
      <link>https://dev.to/divya24/i-added-hindsight-memory-to-an-ml-agent-f1-didnt-move-1i4f</link>
      <guid>https://dev.to/divya24/i-added-hindsight-memory-to-an-ml-agent-f1-didnt-move-1i4f</guid>
      <description>&lt;p&gt;I expected the agent with memory to win. After 120 training runs on a churn-prediction task, the mean test F1 was 0.573 for the agent without memory and 0.574 for the one with it. The difference showed up somewhere else, and one of my own bugs nearly hid it.&lt;br&gt;
This is what I built, how I used Hindsight, and what the numbers did and didn't show.&lt;br&gt;
The problem I wanted to fix&lt;br&gt;
Training a model means running dozens of experiments: different algorithms, settings and features. A few weeks later, nobody remembers what was tried, what failed and why. People repeat dead ends, and the lessons leave when a teammate does.&lt;br&gt;
I called the project Ninaivu, which means "memory" in Tamil. It is an ML lab notebook agent. It remembers every experiment, including the failures, and uses that history to propose the next run. The code is public at github.com/divya240707/Ninaivu.&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%2Fd0ptreyrrsvsbya842i4.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%2Fd0ptreyrrsvsbya842i4.png" alt="Dashboard" width="800" height="424"&gt;&lt;/a&gt;&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%2Fqeuwhnbn9qqhtrb8c7wn.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%2Fqeuwhnbn9qqhtrb8c7wn.png" alt="Dashboard" width="800" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How it works&lt;/strong&gt;&lt;br&gt;
The loop has four steps:&lt;br&gt;
&lt;strong&gt;Recall&lt;/strong&gt; earlier experiments from memory.&lt;br&gt;
&lt;strong&gt;Propose&lt;/strong&gt; the next configuration with an LLM (openai/gpt-oss-20b on Groq).&lt;br&gt;
&lt;strong&gt;Run it&lt;/strong&gt;: a real scikit-learn training run on the Telco Customer Churn dataset from Kaggle. The agent picks the model (logistic regression, random forest or gradient boosting), its hyperparameters, whether to drop columns, and whether to scale.&lt;br&gt;
&lt;strong&gt;Retain&lt;/strong&gt; the configuration and its result, including failures, back into memory.&lt;br&gt;
A run counts as overfit when train F1 exceeds test F1 by more than 0.15. I treat overfit runs and crashes as "bad runs".&lt;br&gt;
&lt;strong&gt;Where Hindsight fits&lt;/strong&gt;&lt;br&gt;
I used the open-source agent memory system Hindsight through its Python client, with Hindsight Cloud. The Hindsight documentation lists three operations, and I used all three.&lt;br&gt;
Retain runs after every experiment:&lt;br&gt;
client.retain(&lt;br&gt;
    bank_id=bank,&lt;br&gt;
    content=f"Experiment: {json.dumps(config)} -&amp;gt; {json.dumps(result)}",&lt;br&gt;
    context="ML experiment log on churn classifier",&lt;br&gt;
)&lt;br&gt;
Recall runs before every proposal in sessions 2 and 3:&lt;br&gt;
found = client.recall(&lt;br&gt;
    bank_id=bank,&lt;br&gt;
    query="Which configurations overfit, errored, or scored best, and what should be tried next?",&lt;br&gt;
)&lt;br&gt;
recalled = [r.text for r in found.results[:12]]&lt;br&gt;
Reflect powers the "Ask the memory" page in the demo app, where you can type a question and get an answer from the stored experiments.&lt;br&gt;
One thing surprised me. Hindsight does not hand your raw log lines back. It extracts facts from what you store. Recall returned sentences like "Dropping the customerID column eliminated overfitting in the logistic regression model", not the JSON I had written. That makes the results readable, but it means you should check what comes back against what you stored.&lt;br&gt;
The agent memory explanation from Vectorize describes the idea in general terms. My project is one small, concrete case of it.&lt;br&gt;
&lt;strong&gt;The experiment&lt;/strong&gt;&lt;br&gt;
I wanted a fair test of whether memory helps, so I built two agents with the same model and the same prompt:&lt;br&gt;
Memory agent: recalls from Hindsight at the start of each run in sessions 2 and 3.&lt;br&gt;
Baseline agent: no persistent memory.&lt;br&gt;
Each trial has 3 sessions of 10 runs, and the agent's context is wiped between sessions. That models coming back to a project weeks later. Each trial used its own memory bank, so trials never shared memories. I ran 2 trials per agent, 120 runs in total.&lt;br&gt;
&lt;strong&gt;What the numbers showed&lt;/strong&gt;&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%2Fotxcgik9o8mbux7krrwd.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%2Fotxcgik9o8mbux7krrwd.png" alt="Comparison" width="799" height="322"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Test F1 did not move. Mean test F1 was 0.573 for the baseline and 0.574 for the memory agent. No session differed by more than 0.001. The F1 ceiling for these model families on this dataset looks like it sits around 0.60, which leaves little room for any agent to improve.&lt;br&gt;
Bad runs moved a little. In sessions 2 and 3, the memory agent made 4 bad runs out of 40. The baseline made 9 out of 40. In session 1, where neither agent has memory, the counts were 4 and 5, so there is already a gap of one run from chance alone. With 2 trials I cannot rule out chance for the later sessions either. I treat it as a pattern worth testing, not a result.&lt;br&gt;
The trap I planted didn't work. The dataset has a customerID column. A model that memorizes IDs overfits, and I expected memory to teach the agent to drop it. In a manual check, dropping it removed the logistic regression overfitting gap (train F1 0.7543 and test F1 0.6047 became 0.6031 and 0.607). But the baseline agent dropped the column in all 60 of its runs, and the memory agent in 52 of 60. The language model already knew to drop an ID column. Memory had nothing to teach it there.&lt;br&gt;
&lt;strong&gt;The bug that almost ruined the comparison&lt;/strong&gt;&lt;br&gt;
My first set of trials looked promising: by the third session the memory agent had zero bad runs. Then I added the fallback column to my results and looked closer.&lt;br&gt;
When the LLM call failed twice, my code quietly substituted a random configuration:&lt;br&gt;
return random_config(), True&lt;br&gt;
The failures were Groq rate limits. The free tier has a daily token cap, and my runs had used almost all of it. Nearly a third of one memory trial was random configurations, not agent decisions. I discarded that whole set, kept the file for reference, and made rate limits fatal:&lt;br&gt;
except Exception as exc:&lt;br&gt;
    if type(exc).&lt;strong&gt;name&lt;/strong&gt; == "RateLimitError":&lt;br&gt;
        raise SystemExit(f"rate limited: {str(exc)[:300]}")&lt;br&gt;
    print(f"proposal failed: {type(exc).&lt;strong&gt;name&lt;/strong&gt;}: {str(exc)[:200]}")&lt;br&gt;
    time.sleep(2)&lt;br&gt;
I reran on a different model and the new data had 0 fallback runs. The "zero bad runs" result disappeared. That one change turned an exciting chart into an honest one.&lt;br&gt;
&lt;strong&gt;Asking the memory a question&lt;/strong&gt;&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%2F1zdxhnke142mx5d0ihrc.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%2F1zdxhnke142mx5d0ihrc.png" alt="Memory" width="799" height="346"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The demo app has a page where you ask the memory a question, for example "Why should we drop the customerID column?". Next to the answer it shows the stored memories it drew from.&lt;br&gt;
The first version of that page was too chatty. Its answer included the right numbers (0.7543 against 0.6047, then 0.6031 against 0.607), but it also said that dropping the column had made test scores "more stable and reliable". My own data says the opposite: the test score did not change. I rewrote the prompt to demand short answers that quote stored numbers, and I kept the retrieved memories on screen so you can check the answer against its sources.&lt;br&gt;
&lt;strong&gt;Lessons&lt;/strong&gt;&lt;br&gt;
Test whether your baseline already knows what your memory is supposed to teach. My planted trap taught me that the base model was ahead of my idea.&lt;br&gt;
Make failures loud. A silent fallback turned an API limit into fake agent behavior. Log it, count it, and stop on it.&lt;br&gt;
Count wasted runs as well as scores. When a metric has a ceiling, memory may show up as fewer mistakes, not a higher number.&lt;br&gt;
Put sources next to generated answers. A memory system is only useful if you can see what it remembered.&lt;br&gt;
Be honest about sample size. With 2 trials per agent, I can describe patterns, not claim effects.&lt;br&gt;
&lt;strong&gt;Limitations&lt;/strong&gt;&lt;br&gt;
One dataset and 2 trials per agent.&lt;br&gt;
The demo memory bank is seeded from logged runs and a few manual checks, not from a live run.&lt;br&gt;
My "repeated failure" metric only counts exact repeats, so it almost never fires, and I draw no conclusions from it.&lt;br&gt;
The code, data and results are all in the Ninaivu repository. If you want to try agent memory yourself, the Hindsight documentation is the best place to start.&lt;/p&gt;

&lt;h1&gt;
  
  
  AIAgents #AgentMemory #Hindsight #LLM
&lt;/h1&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>machinelearning</category>
    </item>
  </channel>
</rss>
