<?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: Manoj K</title>
    <description>The latest articles on DEV Community by Manoj K (@neox7).</description>
    <link>https://dev.to/neox7</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%2F3977685%2F7f983e5c-d2b3-4dc2-9a69-87e7a388fa7f.jpeg</url>
      <title>DEV Community: Manoj K</title>
      <link>https://dev.to/neox7</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/neox7"/>
    <language>en</language>
    <item>
      <title>Why my 0.8 cosine threshold failed in an offline AI memory</title>
      <dc:creator>Manoj K</dc:creator>
      <pubDate>Fri, 06 Nov 2026 04:37:37 +0000</pubDate>
      <link>https://dev.to/neox7/why-my-08-cosine-threshold-failed-in-an-offline-ai-memory-3nf2</link>
      <guid>https://dev.to/neox7/why-my-08-cosine-threshold-failed-in-an-offline-ai-memory-3nf2</guid>
      <description>&lt;p&gt;I'm a self-taught robotics engineering student in Bengaluru. I built Neo, a fully offline AI assistant that remembers facts about you across sessions. It runs llama3.1 through Ollama, stores vectors in LanceDB and facts in SQLite, and does web search through a self-hosted SearXNG. No cloud, no API keys.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;When you tell the assistant "I stopped liking chess", it has to decide what that means. Is it a new fact, an update to an old one, or a retraction?&lt;/p&gt;

&lt;p&gt;My first design used one rule: if cosine similarity with an old fact is above 0.8, treat it as a contradiction.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I measured
&lt;/h2&gt;

&lt;p&gt;I tested 10 real sentence pairs with my actual embedding model through Ollama. The 0.8 rule missed paraphrases and updates. Unrelated pairs scored only 0.29 to 0.41, so the useful range sat much lower than I had assumed.&lt;/p&gt;

&lt;p&gt;I also found an earlier mistake. My retrieval eval was measuring L2 distance, not cosine. After fixing the metric, Recall@k came out at 0.95 and MRR at 0.925.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;I stopped asking cosine to make the decision. Now it only finds candidates, using a low threshold of about 0.5. Then the flow is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;new fact
   ↓
find candidates (cosine ≥ 0.5, active facts only)
   ↓
no candidates → ADD
candidates found → LLM returns {action, target_id}
   ↓
code validates the answer
   ↓
write to SQLite + LanceDB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The LLM picks one of ADD, UPDATE, DELETE or NONE, plus a target id. Plain code then checks the answer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;An unknown or malformed target id falls back to ADD.&lt;/li&gt;
&lt;li&gt;An UPDATE whose target text is identical to the new fact becomes NONE.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The model proposes and the code decides what is allowed. This mattered because llama3.1 sometimes returned UPDATE for a fact that already existed, or picked the wrong target. Prompt tweaks helped, but the guards in code are what made it reliable.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell others
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Test your threshold on real sentence pairs before trusting it.&lt;/li&gt;
&lt;li&gt;Check which metric your eval actually uses (L2 vs cosine).&lt;/li&gt;
&lt;li&gt;Keep the LLM's output narrow and validate it in code.&lt;/li&gt;
&lt;li&gt;Write tests with fake LLM calls so you can check the gate logic without Ollama running.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Honest notes
&lt;/h2&gt;

&lt;p&gt;v1.9 was mostly written with Claude and turned out unstable, so I rebuilt v1.9.5 by hand from v1.8.5 to understand every part. The README credits the AI help. Known limits are listed in KNOWN_ISSUES.md. For example, comparative preferences aren't extracted, and follow-up web queries lose context. It also needs hardware that can run an 8B model locally.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/Neo-X7/neo-AI" rel="noopener noreferrer"&gt;https://github.com/Neo-X7/neo-AI&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;How would you handle contradicting facts in a memory system? I'd like to hear other approaches and how can I improve things in the future.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>showdev</category>
      <category>llm</category>
    </item>
    <item>
      <title>How I fixed silent Ollama failures in my local AI Assistant</title>
      <dc:creator>Manoj K</dc:creator>
      <pubDate>Wed, 10 Jun 2026 12:32:01 +0000</pubDate>
      <link>https://dev.to/neox7/how-i-fixed-silent-ollama-failures-in-my-local-ai-assistant-54n3</link>
      <guid>https://dev.to/neox7/how-i-fixed-silent-ollama-failures-in-my-local-ai-assistant-54n3</guid>
      <description>&lt;p&gt;&lt;strong&gt;How I fixed silent Ollama failures in my local AI assistant&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Neo-AI is an offline assistant with episodic memory, running entirely on-device using Ollama, SQLite, and LanceDB — no cloud, no data leaks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Failure&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;While testing before a release, the CLI showed a generic "An error occurred." I opened the log file and found the real error:&lt;br&gt;
ERROR: Failed to connect to Ollama. Please check that Ollama is downloaded, running and accessible.&lt;br&gt;
Ollama was installed but not running. Neo had no way to start it automatically.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The naive fix&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;start_ollama&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;httpx&lt;/span&gt;
        &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:11434&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Starting OLlama server...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dim&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Popen&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ollama&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;serve&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEVNULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEVNULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ollama server started.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dim&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This uses a hardcoded health check — a GET request to localhost:11434. If Ollama responds, skip. If not, spawn it as a background process using subprocess.Popen, which launches Ollama silently without blocking Neo. stdout and stderr are discarded via DEVNULL so Ollama's internal logs don't pollute Neo's terminal.&lt;br&gt;
The flaw: time.sleep(2) is a guess. On a cold WSL start, Ollama can take longer. Neo proceeds before Ollama is actually ready.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Better Fix&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;start_ollama&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;is_running&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;httpx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:11434&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
        &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;is_running&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Starting Ollama server...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dim&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Popen&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ollama&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;serve&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEVNULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DEVNULL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;  &lt;span class="c1"&gt;# wait up to 10 seconds
&lt;/span&gt;        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;is_running&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ollama server started.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dim&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Ollama failed to start after 10 seconds.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Instead of sleeping blindly, the retry loop polls every second for up to 10 seconds. Neo only proceeds once Ollama confirms it's alive. If it never starts, a RuntimeError is raised and logged.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Takeaway&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Local LLM reliability is an engineering problem, not a hardware problem. Don't sleep — poll.&lt;br&gt;
&lt;a href="https://github.com/Neo-X7/neo-AI" rel="noopener noreferrer"&gt;Neo-AI on GitHub&lt;/a&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>opensource</category>
      <category>machinelearning</category>
      <category>ollama</category>
    </item>
  </channel>
</rss>
