<?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: Sourabha Kallapur</title>
    <description>The latest articles on DEV Community by Sourabha Kallapur (@sourabha_kallapur_ef0353d).</description>
    <link>https://dev.to/sourabha_kallapur_ef0353d</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%2F4016545%2F282ae704-9427-4dc9-b242-2c994cc3f5c3.png</url>
      <title>DEV Community: Sourabha Kallapur</title>
      <link>https://dev.to/sourabha_kallapur_ef0353d</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sourabha_kallapur_ef0353d"/>
    <language>en</language>
    <item>
      <title>I Built a Research Memory Agent with Cognee—Five API Breaks, One Knowledge Graph, Seven Days</title>
      <dc:creator>Sourabha Kallapur</dc:creator>
      <pubDate>Sun, 05 Jul 2026 18:18:14 +0000</pubDate>
      <link>https://dev.to/sourabha_kallapur_ef0353d/i-built-a-research-memory-agent-with-cognee-five-api-breaks-one-knowledge-graph-seven-days-e3b</link>
      <guid>https://dev.to/sourabha_kallapur_ef0353d/i-built-a-research-memory-agent-with-cognee-five-api-breaks-one-knowledge-graph-seven-days-e3b</guid>
      <description>&lt;h1&gt;
  
  
  I applied strict TDD to build a Cognee knowledge graph agent — here is every upstream API break I hit and how I fixed it
&lt;/h1&gt;

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

&lt;p&gt;ChronoScholar is a temporally-aware research memory agent that ingests arXiv papers into a Cognee knowledge graph and detects when stored scientific beliefs are contradicted by incoming literature. Built for the WeMakeDevs x Cognee hackathon.&lt;/p&gt;

&lt;p&gt;The core problem: standard RAG returns answers from all stored papers with equal confidence. An agent that ingested Paper A in January and Paper B in March — where B refutes A's central claim — surfaces both indefinitely. Fixing this is not a retrieval problem. It requires persistent, typed memory with explicit contradiction awareness.&lt;/p&gt;

&lt;p&gt;The system ingests papers via the arXiv API, builds a typed knowledge graph using cognee.add() and cognee.cognify(), classifies paper pairs with Gemini 2.5 Flash, and synthesizes cross-paper answers using Cognee's GRAPH_COMPLETION search mode.&lt;/p&gt;

&lt;p&gt;Pilot numbers: 10 papers ingested, 494 knowledge graph entities, 1,025 edges, 6 typed edge types (contradicts, supports, extends, invalidates, replicates, authored_by). Contradiction detection F1=1.000 on a 10-pair benchmark — 4 positive, 6 negative. Results are directional; n=10 is insufficient for confidence interval estimation.&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/SourabhaKK/ChronoScholar" rel="noopener noreferrer"&gt;https://github.com/SourabhaKK/ChronoScholar&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Why TDD on an AI Memory System Is Different
&lt;/h2&gt;

&lt;p&gt;Standard TDD works well on deterministic code. You define inputs, specify expected outputs, mock external dependencies, and confirm behavior. The tests stay fast and the feedback loop stays tight.&lt;/p&gt;

&lt;p&gt;AI memory systems break this model in two distinct ways. First, the behavior you care about is often emergent from library internals you do not control — Cognee's auth posture, LiteLLM's model routing, LadybugDB's file locking. Second, mocking these dependencies hides their real behavior by design.&lt;/p&gt;

&lt;p&gt;I ran strict Red→Green→Refactor across 8 components and 60 tests, with all external calls mocked. The test suite was valuable. It also missed every integration failure.&lt;/p&gt;

&lt;h3&gt;
  
  
  What TDD caught
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;B005 strip() bug.&lt;/strong&gt; The contradiction response parser called &lt;code&gt;response.strip("&lt;/code&gt;`&lt;code&gt;json")&lt;/code&gt;. Python's str.strip() strips individual characters from a set, not a substring. This silently corrupted JSON parsing whenever the model output started with a backtick. The test caught it by asserting on the parsed dict, not the raw string.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;System prompt never passed to Groq.&lt;/strong&gt; The detect() method constructed a detailed system prompt and passed it to groq_complete(), but the function signature accepted only &lt;code&gt;prompt&lt;/code&gt;. The system prompt was silently discarded. The fix required a keyword argument and a test that mocked the Groq client and asserted on the messages list passed to the API call. Without that assertion, detect() returned plausible-looking output with no scientific grounding and no visible error.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;string.Template greedy parsing.&lt;/strong&gt; Prompt templates used &lt;code&gt;$variable&lt;/code&gt; substitution. Template substitutes greedily — &lt;code&gt;$var&lt;/code&gt; in a sentence could match a longer key than intended, leaving substitution gaps. Switching to &lt;code&gt;${variable}&lt;/code&gt; throughout, with tests asserting each variable appeared exactly once in rendered output, caught two silent failures.&lt;/p&gt;

&lt;h3&gt;
  
  
  What TDD could not catch
&lt;/h3&gt;

&lt;p&gt;All 60 tests mock cognee.add(), cognee.cognify(), and cognee.search(). The mocks return controlled values. Real Cognee does not.&lt;/p&gt;

&lt;p&gt;Every one of the five integration failures described below was invisible to the test suite until the real system ran. The mocks were correct relative to the API surface documented when the tests were written. The actual library behavior differed.&lt;/p&gt;

&lt;p&gt;The lesson is specific: AI memory systems need a second test tier — integration tests that run against real dependencies on a minimal one-document corpus, with real API keys, in CI. Unit tests with mocks are necessary but not sufficient.&lt;/p&gt;




&lt;h2&gt;
  
  
  Five Cognee 1.2.2 Integration Failures
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Failure 1: set_llm_config() rejected
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; &lt;code&gt;cognee.config.set_llm_config(provider="groq", model="llama-3.1-8b-instant", api_key=key)&lt;/code&gt; raised &lt;code&gt;InvalidConfigAttributeError&lt;/code&gt; on the &lt;code&gt;provider&lt;/code&gt; key at startup.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Root cause:&lt;/strong&gt; Cognee 1.2.2 removed programmatic LLM configuration. All LLM config is now read from environment variables via LiteLLM.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Remove the set_llm_config() call entirely. Set &lt;code&gt;LLM_MODEL&lt;/code&gt; and &lt;code&gt;LLM_API_KEY&lt;/code&gt; in .env.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; Cognee routes through LiteLLM internally. Model names require LiteLLM provider prefix format — &lt;code&gt;groq/llama-3.1-8b-instant&lt;/code&gt;, not &lt;code&gt;llama-3.1-8b-instant&lt;/code&gt;. Check the LiteLLM model list, not the provider's native model list.&lt;/p&gt;




&lt;h3&gt;
  
  
  Failure 2: get_nodes() and get_edges() do not exist
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; &lt;code&gt;AttributeError&lt;/code&gt; on &lt;code&gt;graph_engine.get_nodes()&lt;/code&gt; inside CogneeService.get_stats().&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Root cause:&lt;/strong&gt; The internal graph engine API changed between versions with no changelog entry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Replace with get_graph_data(), which returns a &lt;code&gt;(nodes, edges)&lt;/code&gt; tuple.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;`python&lt;br&gt;
nodes, edges = await graph_engine.get_graph_data()&lt;br&gt;
`&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; Cognee's internal graph API has no stability guarantee. Wrap all internal API calls in try/except with a fallback that returns zero counts — the stats endpoint should degrade gracefully, not crash.&lt;/p&gt;




&lt;h3&gt;
  
  
  Failure 3: Multi-tenant auth reads the wrong database
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; entity_count=0 after successful cognify(). The knowledge graph appeared empty on every query.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Root cause:&lt;/strong&gt; &lt;code&gt;ENABLE_BACKEND_ACCESS_CONTROL=true&lt;/code&gt; is the default. In multi-tenant mode, cognify() writes to a UUID-scoped &lt;code&gt;.lbug&lt;/code&gt; file while get_graph_data() reads the global file. Different databases — cognify writes, queries read nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Set &lt;code&gt;ENABLE_BACKEND_ACCESS_CONTROL=false&lt;/code&gt; in .env. This requires load_dotenv() to execute before any &lt;code&gt;app.*&lt;/code&gt; import, because Cognee resolves its auth posture at module import time.&lt;/p&gt;

&lt;p&gt;`&lt;code&gt;&lt;/code&gt;python&lt;/p&gt;

&lt;h1&gt;
  
  
  app/main.py — order is mandatory
&lt;/h1&gt;

&lt;p&gt;from dotenv import load_dotenv&lt;br&gt;
load_dotenv()&lt;br&gt;
from app.config import Settings  # Cognee imports happen inside app modules&lt;br&gt;
&lt;code&gt;&lt;/code&gt;`&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; Module-level side effects create import-order dependencies that unit tests cannot detect. If load_dotenv() runs after Cognee's internal modules initialize, the env var arrives too late.&lt;/p&gt;




&lt;h3&gt;
  
  
  Failure 4: Windows file lock contention (Error 33)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Both CHUNKS and GRAPH_COMPLETION searches in the /compare endpoint failed with &lt;code&gt;"Could not set lock on cognee_graph_ladybug"&lt;/code&gt; when run concurrently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Root cause:&lt;/strong&gt; LadybugDB is a single-file graph store. On Windows, the OS acquires an exclusive lock. Concurrent async reads both attempt to acquire it and one fails.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Run CHUNKS first, await completion, then run GRAPH_COMPLETION. Sequential, not concurrent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; File-backed stores have OS-specific concurrency semantics. LadybugDB works with concurrent readers on Linux. On Windows it does not. Test on the target OS.&lt;/p&gt;




&lt;h3&gt;
  
  
  Failure 5: Groq 6K TPM shared between two LLM consumers
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; GRAPH_COMPLETION synthesis returned 7 characters. No exception raised, no error logged.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Root cause:&lt;/strong&gt; ContradictionService.detect() and Cognee's internal synthesis both used &lt;code&gt;LLM_API_KEY&lt;/code&gt; pointing to Groq. A single GRAPH_COMPLETION synthesis call requests 6,910 tokens — over Groq's 6,000 TPM free-tier limit. Cognee exhausted the budget silently and returned an empty result.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Assign separate providers.&lt;/p&gt;

&lt;p&gt;`&lt;code&gt;&lt;/code&gt;ini&lt;/p&gt;

&lt;h1&gt;
  
  
  .env
&lt;/h1&gt;

&lt;p&gt;APP_LLM_PROVIDER=gemini      # application LLM (detect, query grounding)&lt;br&gt;
LLM_MODEL=openai/gpt-4o-mini # Cognee internal LLM&lt;br&gt;
GROQ_API_KEY=...             # tier-2 fallback only&lt;br&gt;
&lt;code&gt;&lt;/code&gt;`&lt;/p&gt;

&lt;p&gt;Gemini 2.5 Flash for the application layer. GPT-4o-mini for Cognee internals. Groq as tier-2 fallback. Three separate TPM pools, no contention.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lesson:&lt;/strong&gt; Any library that makes its own LLM calls is a separate consumer of your rate limit budget. Assign it a dedicated API key.&lt;/p&gt;




&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;10 papers ingested. 494 knowledge graph entities. 1,025 edges across 6 typed edge types. Cognify runtime: 63.3 seconds per 10-paper batch. Contradiction detection: Precision=1.000, Recall=1.000, F1=1.000 on a 10-pair pilot benchmark (4 positive, 6 negative).&lt;/p&gt;

&lt;p&gt;Statistical caveat: n=10 is a pilot evaluation. Results are directional only. Sample size is insufficient for confidence interval estimation.&lt;/p&gt;

&lt;p&gt;One additional finding: Gemini 2.5 Flash correctly classified all 4 positive pairs with zero false negatives. Groq llama-3.1-8b-instant missed one, giving F1=0.857 on the same benchmark. Model capability on scientific NLI is not uniform across providers.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Is Next
&lt;/h2&gt;

&lt;p&gt;Expand the benchmark to 200+ pairs to reach statistical validity. Add a second CI test tier: integration tests against real Cognee on a one-document corpus, with real API keys in a separate workflow.&lt;/p&gt;

&lt;p&gt;Run systematic provider comparison — Groq vs Gemini vs GPT-4o-mini — on contradiction detection F1 to quantify the capability gap and understand at what scale it matters.&lt;/p&gt;

&lt;p&gt;GitHub: &lt;a href="https://github.com/SourabhaKK/ChronoScholar" rel="noopener noreferrer"&gt;https://github.com/SourabhaKK/ChronoScholar&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  python #cognee #knowledgegraph #tdd #machinelearning
&lt;/h1&gt;

</description>
      <category>cognee</category>
      <category>python</category>
      <category>machinelearning</category>
      <category>tdd</category>
    </item>
  </channel>
</rss>
