<?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: Gauarv Chaudhary</title>
    <description>The latest articles on DEV Community by Gauarv Chaudhary (@anamasgard).</description>
    <link>https://dev.to/anamasgard</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%2F3921264%2F1a26b2c1-1fab-45ad-9085-446d9d3403ee.jpeg</url>
      <title>DEV Community: Gauarv Chaudhary</title>
      <link>https://dev.to/anamasgard</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/anamasgard"/>
    <language>en</language>
    <item>
      <title>I built an agent that refuses to connect two files unless the graph already did</title>
      <dc:creator>Gauarv Chaudhary</dc:creator>
      <pubDate>Sun, 18 Oct 2026 18:23:29 +0000</pubDate>
      <link>https://dev.to/anamasgard/graph-hackthon-2cg5</link>
      <guid>https://dev.to/anamasgard/graph-hackthon-2cg5</guid>
      <description>&lt;p&gt;Built for the WeMakeDevs × FalkorDB Graph Hacks hackathon. Track: Agents That Act on Connected Data.&lt;/p&gt;

&lt;p&gt;There are two ways to ask an AI about a codebase you never read, and both of them are wrong.&lt;/p&gt;

&lt;p&gt;The first hands the repository to a model and lets it describe the structure. It is fast, and a dependency that is ninety percent right is worse than no dependency, because there is no way to tell which ten percent is wrong. The moment a model is allowed to guess an edge, every edge becomes a maybe.&lt;/p&gt;

&lt;p&gt;The second draws a picture. Folders as boxes, imports as lines, a canvas you can pan. If you take the database away and the picture still answers "what imports this?", the database was decoration. Graph Hacks says that in the rules, and it should. A visualisation on top of ordinary storage does not qualify.&lt;/p&gt;

&lt;p&gt;I wanted the picture and the answer to be the same object. A parser reads the TypeScript and JavaScript and proves each edge. FalkorDB stores those edges. The agent may only ask questions that graph can answer, through tools that run fixed queries. It does not get to write Cypher. It does not get to say two files are connected because that seems likely.&lt;/p&gt;

&lt;p&gt;That is Graph Verse.&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%2F0wskvqjrmi6ujav10gqw.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%2F0wskvqjrmi6ujav10gqw.png" alt=" " width="800" height="501"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/ANAMASGARD/graph-verse" rel="noopener noreferrer"&gt;https://github.com/ANAMASGARD/graph-verse&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Live: &lt;a href="https://graph-verse.vercel.app/" rel="noopener noreferrer"&gt;https://graph-verse.vercel.app/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What it actually does
&lt;/h2&gt;

&lt;p&gt;You paste a public &lt;code&gt;github.com/owner/name&lt;/code&gt;. No token. Nothing private.&lt;/p&gt;

&lt;p&gt;Graph Verse then, on its own:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fetches the public tarball and records the commit.&lt;/li&gt;
&lt;li&gt;Parses the tree. A resolved import, re-export, dynamic &lt;code&gt;import()&lt;/code&gt;, or &lt;code&gt;require()&lt;/code&gt; becomes an edge. An import it cannot resolve is counted, with a reason, and is never stored as a line.&lt;/li&gt;
&lt;li&gt;Writes the result into that team's FalkorDB graph. One graph per Clerk organization. Switching team switches graph. There is no organization column to filter on.&lt;/li&gt;
&lt;li&gt;Draws the map from those nodes. Click a file and the detail pane is a walk of the edges already stored: what it imports, what imports it, what sits two hops out.&lt;/li&gt;
&lt;li&gt;Lets you ask, in a panel that shows each tool call as it happens.&lt;/li&gt;
&lt;li&gt;Stops the model from inventing a path. If a tool did not return a path, that path is not in the answer.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Step 6 is the whole project. Everything before it is there so the agent has a graph worth walking.&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%2F8ojtwexkr871lugnwuda.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%2F8ojtwexkr871lugnwuda.png" alt="Dashboard: paste a public GitHub URL" width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The part I care about: the edge is real
&lt;/h2&gt;

&lt;p&gt;Anyone can tell a model "only use real imports" and hope. That is a convention. The model can ignore it. A later prompt can soften it. The answer still looks confident.&lt;/p&gt;

&lt;p&gt;In Graph Verse the split is structural.&lt;/p&gt;

&lt;p&gt;The parser does not know Next, React, or FalkorDB exist. It takes a directory and returns files, edges, and a coverage report. An edge exists only when a specifier resolved to a file that is actually in the tree. &lt;code&gt;IMPORTS&lt;/code&gt;, &lt;code&gt;RE_EXPORTS&lt;/code&gt;, &lt;code&gt;DYNAMIC_IMPORT&lt;/code&gt;, and &lt;code&gt;REQUIRES&lt;/code&gt; are the only ways two files get connected.&lt;/p&gt;

&lt;p&gt;The agent does not see the database. It is a separate process. It is never told which analysis it is reading. A short-lived credential, minted on the server, names one organization and one analysis. The six tools call back into the app, and the app runs one fixed query each:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;summary of the repository&lt;/li&gt;
&lt;li&gt;search by part of a path&lt;/li&gt;
&lt;li&gt;list files by role&lt;/li&gt;
&lt;li&gt;direct neighbours&lt;/li&gt;
&lt;li&gt;a walk of at most two hops, in or out, including a shortest chain between two files&lt;/li&gt;
&lt;li&gt;the route table&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The walk is Cypher, variable length, capped at two:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;a:&lt;/span&gt;&lt;span class="n"&gt;Analysis&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;id:&lt;/span&gt; &lt;span class="n"&gt;$id&lt;/span&gt;&lt;span class="ss"&gt;})&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:HAS_FILE&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;start:&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;path:&lt;/span&gt; &lt;span class="n"&gt;$path&lt;/span&gt;&lt;span class="ss"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;MATCH&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="k"&gt;start&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:IMPORTS&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;RE_EXPORTS&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;DYNAMIC_IMPORT&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;REQUIRES&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;other&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;other&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;start&lt;/span&gt;
&lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="n"&gt;other.path&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;length&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;depth&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;depth&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the question needs a third hop, the instructions say to walk again from a file the first walk returned. They do not say to guess the rest.&lt;/p&gt;

&lt;p&gt;The browser never opens FalkorDB. If the agent process is down, the map, the blast radius, and Explain still work, because those read the same stored edges. If FalkorDB is down, none of them work. That is the test I kept using. A product that still answers with the graph removed has a picture, not a graph.&lt;/p&gt;

&lt;h2&gt;
  
  
  How FalkorDB fits
&lt;/h2&gt;

&lt;p&gt;I want to be precise, because "we used the sponsor database" can mean a logo in the README.&lt;/p&gt;

&lt;p&gt;FalkorDB is the context layer. My code does not walk the import graph in memory and then draw the result. The relationships are the product.&lt;/p&gt;

&lt;p&gt;Four things it actually does:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;One graph per team.&lt;/strong&gt; &lt;code&gt;selectGraph(orgId)&lt;/code&gt; opens the Clerk organization's graph. Two teams on one instance do not share nodes. Tenancy is which graph you opened, not a &lt;code&gt;WHERE&lt;/code&gt; clause.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The traversals the UI and the agent share.&lt;/strong&gt; Neighbours, blast radius, dependency chain, and the agent walk are the same four relationship types. Fan-in and fan-out are counts stored on the file. Who depends on a file is a query.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Routes as nodes, only when both ends are known.&lt;/strong&gt; A route is a &lt;code&gt;:Route&lt;/code&gt; with a method, a path, and a line, linked &lt;code&gt;HAS_ROUTE&lt;/code&gt; and &lt;code&gt;DEFINED_IN&lt;/code&gt;. If the parser cannot recover both the method and the full path, it stores none. A wrong route is the same failure as an invented edge.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Memory in that same graph.&lt;/strong&gt; An explanation is written from the file and its real neighbours, then stored on an &lt;code&gt;:AiCache&lt;/code&gt; node. The next request for the same explanation is a graph read. Each Ask is stored as an &lt;code&gt;:Inquiry&lt;/code&gt; on the analysis: the question, the thread, and the paths the tools returned. The next session can show how an earlier answer was reached. That is the memory. It is not a chat log in the browser.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;code&gt;src/components/Layout/index.tsx&lt;/code&gt; in &lt;code&gt;xizon/fullstack-nextjs-app-template&lt;/code&gt; is a file a lot of other files import. What it itself imports is this, not a sentence the model felt like writing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cypher"&gt;&lt;code&gt;&lt;span class="k"&gt;MATCH&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;p:&lt;/span&gt;&lt;span class="n"&gt;Project&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;repoOwner:&lt;/span&gt; &lt;span class="s1"&gt;'xizon'&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="py"&gt;repoName:&lt;/span&gt; &lt;span class="s1"&gt;'fullstack-nextjs-app-template'&lt;/span&gt;&lt;span class="ss"&gt;})&lt;/span&gt;
      &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:HAS_ANALYSIS&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;a:&lt;/span&gt;&lt;span class="n"&gt;Analysis&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
      &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;:HAS_FILE&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;f:&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt; &lt;span class="ss"&gt;{&lt;/span&gt;&lt;span class="py"&gt;path:&lt;/span&gt; &lt;span class="s1"&gt;'src/components/Layout/index.tsx'&lt;/span&gt;&lt;span class="ss"&gt;})&lt;/span&gt;
      &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;[&lt;/span&gt;&lt;span class="py"&gt;r:&lt;/span&gt;&lt;span class="n"&gt;IMPORTS&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;RE_EXPORTS&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;DYNAMIC_IMPORT&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;REQUIRES&lt;/span&gt;&lt;span class="ss"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="py"&gt;other:&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;RETURN&lt;/span&gt; &lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="ss"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="ss"&gt;)&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;kind&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;other.path&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="ss"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r.specifier&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="n"&gt;specifier&lt;/span&gt;
&lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That returns &lt;code&gt;BackToTop&lt;/code&gt;, &lt;code&gt;Footer&lt;/code&gt;, &lt;code&gt;Header&lt;/code&gt;, &lt;code&gt;MultilevelDropdownMenu&lt;/code&gt;, and &lt;code&gt;src/store/actions/demoMenuActions.js&lt;/code&gt;. If the agent names a sixth file, it is wrong, full stop.&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%2Flp1av3r00w8civ28pdni.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%2Flp1av3r00w8civ28pdni.png" alt="Layout selected, with the files it depends on" width="799" height="361"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Proving it is not pattern-matching
&lt;/h2&gt;

&lt;p&gt;One happy question proves nothing. Ask "what imports this?" on a file whose name is &lt;code&gt;Header&lt;/code&gt; and a model will invent a reasonable list. It will look like the tool worked.&lt;/p&gt;

&lt;p&gt;So the checks disagree with each other.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The lookup.&lt;/strong&gt; Ask what &lt;code&gt;Layout&lt;/code&gt; imports. The answer is allowed to contain only paths a tool returned. The five files above are the list. A sixth name, however plausible, fails. There is a separate check that does the same thing to explanations: any token that looks like a source file must have been in the set the model was shown. Score is 1 or 0. A model does not grade the model.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The refusal.&lt;/strong&gt; Ask it to grade the code. Say whether a file is good, safe, or well written. The instructions are to decline in one sentence and say what it can answer instead: where a file is, what it imports, what imports it, what sits two hops out, which routes exist. If it writes a review, the run failed. The product explains a codebase. It does not score one. A score is how people stop reading the map.&lt;/p&gt;

&lt;p&gt;Same agent. Same tools. Same graph. One question must come back with those five paths. The other must come back with no opinion at all.&lt;/p&gt;

&lt;h2&gt;
  
  
  What went wrong
&lt;/h2&gt;

&lt;p&gt;Almost none of this was the model being clever. The failures were the system letting a guess look like a fact, or hiding the graph I had just stored.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Clicking a box made the map vanish.&lt;/strong&gt; Opening a folder recenters the camera on that one node. The rest of the graph left the viewport, which feels exactly like a delete. On top of that, React Flow will draw nothing if its parent has no height, and the canvas was sizing itself from content that is positioned absolutely. The parent collapsed, the library logged that it had no width and no height, and the edges were gone. The fix was to give the canvas the space under the header, and to fit the whole graph after a folder opens, not one box.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The dashboard crashed on a team with no graph yet.&lt;/strong&gt; Listing analyses used a read-only query. FalkorDB answers that with &lt;code&gt;Invalid graph operation on empty key&lt;/code&gt; when that organization's graph has never been written. A session refresh then sent the browser to &lt;code&gt;/dashboard&lt;/code&gt; instead of back to the analysis, and the error overlay replaced the map. An empty team is an empty list. It is not an exception.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The agent would not even compile.&lt;/strong&gt; The dev server refreshes a lockfile and tries to fetch an optional WebAssembly package. npm on this machine refuses remote tarballs unless you allow them. The agent is a separate service. Until that fetch was allowed, Ask had no process to talk to, and the map was the whole product. Which is fine. Ask is allowed to be down. The map is not allowed to lie.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Progress could not use the database's pub/sub.&lt;/strong&gt; The FalkorDB user cannot &lt;code&gt;SUBSCRIBE&lt;/code&gt;. Analysis progress is a server-sent event on an in-process bus, after the server has checked that the analysis id is in the current organization's graph. The browser still never opens a database connection.&lt;/p&gt;

&lt;p&gt;Every one of those would have left a demo that looked finished while the claim was soft. The happy path, a map with lines on it, works either way.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I would tell you if you are building one of these
&lt;/h2&gt;

&lt;p&gt;Put the edge in the parser, not the prompt. "Please do not invent imports" is a wish. A resolved specifier written as a relationship is a property of the system. If your graph story depends on the model cooperating, you do not have a graph.&lt;/p&gt;

&lt;p&gt;Build the question your agent should get wrong. "What does Layout import?" is the easy one, and it can be faked. "Grade this file" is the one that shows whether the refusal is real.&lt;/p&gt;

&lt;p&gt;The strings you return from a tool are the only nouns the model is allowed to use. A path that was not in the tool result does not get to appear because it would make a nicer sentence.&lt;/p&gt;

&lt;p&gt;When the agent behaves strangely, suspect the instruction that was quietly wrong about the world. The two-hop cap is one of those. Past two hops it must start again from a file it already has, and say so. Letting it "just continue" is how invented files come back.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does not do
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;It does not score, rate, or review the code.&lt;/li&gt;
&lt;li&gt;It does not open private repositories. Public GitHub URLs only.&lt;/li&gt;
&lt;li&gt;It does not parse languages other than TypeScript and JavaScript.&lt;/li&gt;
&lt;li&gt;A walk is two hops. Farther than that is another walk, from a file the first one returned.&lt;/li&gt;
&lt;li&gt;A route is stored only when both the method and the full path come back from the syntax.&lt;/li&gt;
&lt;li&gt;Ask needs the agent process running. With it stopped, the map, the blast radius, and Explain still answer from the stored edges.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://graph-verse.vercel.app/" rel="noopener noreferrer"&gt;https://graph-verse.vercel.app/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sign in, create or pick an organization, paste a public repository, and wait for the four stages. Then ask what a file imports, and ask it to grade that file. The first answer should be a list of paths. The second should be a refusal.&lt;/p&gt;

&lt;p&gt;The video is the same run: &lt;a href="https://youtu.be/hz-TFz4LJ4I" rel="noopener noreferrer"&gt;https://youtu.be/hz-TFz4LJ4I&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Source: &lt;a href="https://github.com/ANAMASGARD/graph-verse" rel="noopener noreferrer"&gt;https://github.com/ANAMASGARD/graph-verse&lt;/a&gt;&lt;br&gt;
The graph model, the decision that the parser proves edges and FalkorDB walks them, and the checks above are the part I had to be able to explain without them.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>webdev</category>
      <category>showdev</category>
    </item>
    <item>
      <title>I built a Read.ai connector for Cognee. Importing once wasn’t enough</title>
      <dc:creator>Gauarv Chaudhary</dc:creator>
      <pubDate>Wed, 14 Oct 2026 19:13:21 +0000</pubDate>
      <link>https://dev.to/anamasgard/i-built-a-readai-connector-for-cognee-importing-once-wasnt-enough-3hcj</link>
      <guid>https://dev.to/anamasgard/i-built-a-readai-connector-for-cognee-importing-once-wasnt-enough-3hcj</guid>
      <description>&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%2Fyrpocps9u7ql1bev7nrl.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%2Fyrpocps9u7ql1bev7nrl.png" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Built for WeMakeDevs × Cognee Mergetober.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;A meeting report says the launch is on Friday. Someone corrects it to Monday. Your assistant still retrieves Friday.&lt;/p&gt;

&lt;p&gt;Nothing crashed. The import worked. The copy is just out of date.&lt;/p&gt;

&lt;p&gt;That is the problem behind the Read.ai connector I built for Cognee: importing meeting reports is useful, but those reports do not stop changing once they reach your database.&lt;/p&gt;

&lt;p&gt;The demo below uses fictional reports and synthetic model responses with &lt;strong&gt;real local Cognee storage&lt;/strong&gt;. The screenshots are evidence views, not a new chatbot interface or a production deployment.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it actually does
&lt;/h2&gt;

&lt;p&gt;Choose meeting IDs or a date range, then choose whether to include summaries, transcripts, and metrics. Each available report becomes one document, with the summary first and the transcript last. Participant names, timestamps, and the report link keep the surrounding context attached. Pasted text&lt;/p&gt;

&lt;p&gt;The supported workflow runs through &lt;code&gt;sync_read_ai(...)&lt;/code&gt;, which handles ingestion, recovery, Cognee processing, and cleanup verification. You can then search the selected dataset. Pasted text&lt;/p&gt;

&lt;p&gt;For a project assistant, the intended benefit is straightforward: make meeting context searchable and refresh it on subsequent syncs, instead of manually exporting and uploading reports after every correction. The connector supplies the documents; it does not decide which of two conflicting decisions is authoritative.&lt;/p&gt;

&lt;p&gt;Authentication uses OAuth 2.1, not the API-key wording in the original issue. The &lt;code&gt;read-ai-connect&lt;/code&gt; command handles setup, and token refresh is process-locked so concurrent runs do not refresh the same credential simultaneously. Pasted text&lt;/p&gt;

&lt;h2&gt;
  
  
  An old meeting can still contain a new edit
&lt;/h2&gt;

&lt;p&gt;The tempting approach is to fetch only meetings newer than the last sync.&lt;/p&gt;

&lt;p&gt;But correcting last month’s summary does not change last month’s meeting date. Read.ai’s documented date filters use meeting start time, not report modification time. A date-only cursor would miss that correction.&lt;/p&gt;

&lt;p&gt;So the connector uses &lt;strong&gt;full upstream reconciliation with incremental downstream processing&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In plain English: recheck the selected reports, render their content consistently, and compare hashes. Under normal operation, send only new or changed content downstream.&lt;/p&gt;

&lt;p&gt;This avoids repeated extraction and embedding work for unchanged documents. It does &lt;strong&gt;not&lt;/strong&gt; avoid rereading the selected reports from Read.ai. Pasted text&lt;/p&gt;

&lt;p&gt;The successful checkpoint comes last—after ingestion, Cognee’s processing step, and verification. Loading data into staging is not the same as finishing the sync. Pasted text&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%2F5hnxmendltnq91xvcl4g.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%2F5hnxmendltnq91xvcl4g.png" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The implementation flow. Incomplete reconciliation stops before new staging changes; later failures can leave unfinished work without advancing the successful checkpoint.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Two reports, then the changes that matter
&lt;/h2&gt;

&lt;p&gt;The local demonstration starts with two fictional reports: a Friday launch plan and a support handover. Both become searchable documents.&lt;/p&gt;

&lt;p&gt;Run the same selection again, and both Data IDs stay the same. The measured synchronization deltas are zero extractions and zero embeddings. Those counters are checked &lt;strong&gt;before another search&lt;/strong&gt;, so query work is not mixed into the sync measurement.&lt;/p&gt;

&lt;p&gt;Next, the launch report changes from Friday to Monday. Its meeting ID stays the same. One document changes, the other stays unchanged, and the old document’s references and chunk vectors are removed.&lt;/p&gt;

&lt;p&gt;The retrieved chunk contains:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Alphacorp and Sharedcorp moved the launch to Monday after the security review.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is stored report text, not a generated answer.&lt;/p&gt;

&lt;p&gt;Then the launch report is removed from the synthetic provider. Its document and owned chunks disappear during that sync. The handover report and the shared entity it still references survive.&lt;/p&gt;

&lt;p&gt;Finally, a simulated &lt;code&gt;403&lt;/code&gt; permission failure leaves the sync incomplete and preserves the previous corpus and successful checkpoint.&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%2Ft5en2jg0cf2y9743xx26.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%2Ft5en2jg0cf2y9743xx26.png" alt=" " width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Synthetic reports and model responses, real local storage. The five phases check updates, reuse, removal, and failure handling—not the quality of a real model’s answers.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Missing is not enough evidence to delete
&lt;/h2&gt;

&lt;p&gt;For a date-based selection, a previously imported report must be absent from a complete inventory, return &lt;code&gt;404&lt;/code&gt; on retrieval, and remain absent from a second complete inventory. All of that happens within the same sync attempt. Explicit-ID selections use two detail requests.&lt;/p&gt;

&lt;p&gt;A broken page or permission error is not treated as a deletion instruction.&lt;/p&gt;

&lt;p&gt;There is a trade-off: previously imported content can remain locally accessible while permissions are uncertain. &lt;strong&gt;This is not complete access-control synchronization.&lt;/strong&gt; Pasted text&lt;/p&gt;

&lt;h2&gt;
  
  
  What passed—and what still needs work
&lt;/h2&gt;

&lt;p&gt;The fresh run shown below reports &lt;strong&gt;149 passing tests on Python 3.12.15&lt;/strong&gt;, with no failures, errors, or skips. Ruff lint, formatting, and the existing offline example also passed. The test run emitted 1,559 warnings.&lt;/p&gt;

&lt;p&gt;This run used Cognee 1.6.4, dlt 1.30.0, SQLite, Ladybug, and LanceDB. Only Python 3.12 was exercised in this particular evidence run.&lt;/p&gt;

&lt;p&gt;An earlier harness attempt had setup errors. The screenshot records those separately: the external scripts were corrected, the original logs retained, and the connector tests left unchanged.&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%2Ftgfsga78o1qqp5kn3wf9.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%2Ftgfsga78o1qqp5kn3wf9.png" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Fresh local verification for commit &lt;code&gt;130f52d34ec2&lt;/code&gt;. Provider and model responses are synthetic; this is not hosted CI or live-provider acceptance.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;There is also a known Cognee 1.6.4 cleanup limitation. A relationship-type vector can remain after its graph node is removed if vector deletion fails. A later retry does not automatically repair it.&lt;/p&gt;

&lt;p&gt;The connector detects that leftover vector, raises &lt;code&gt;VerificationError&lt;/code&gt;, and refuses to advance the successful checkpoint. Repair still requires a core change or independently verified operator intervention.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A passing test that detects this failure does not mean the failure is fixed.&lt;/strong&gt; Pasted text&lt;/p&gt;

&lt;p&gt;Live Read.ai OAuth, report edits, field availability, and deletion/permission transitions remain unverified. The local results should not be read as proof of a production-ready integration. Pasted text&lt;/p&gt;

</description>
      <category>python</category>
      <category>ai</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
    <item>
      <title>I built an Airtable connector for Cognee. The hard part was knowing what to delete.</title>
      <dc:creator>Gauarv Chaudhary</dc:creator>
      <pubDate>Fri, 09 Oct 2026 06:23:23 +0000</pubDate>
      <link>https://dev.to/anamasgard/i-built-an-airtable-connector-for-cognee-the-hard-part-was-knowing-what-to-delete-5605</link>
      <guid>https://dev.to/anamasgard/i-built-an-airtable-connector-for-cognee-the-hard-part-was-knowing-what-to-delete-5605</guid>
      <description>&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%2Flvph85dk3r3p93o0i39x.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%2Flvph85dk3r3p93o0i39x.png" alt=" " width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A project record gets deleted. Its old context should leave Cognee on the next successful sync.&lt;/p&gt;

&lt;p&gt;An Airtable request fails. The last successfully imported data should stay.&lt;/p&gt;

&lt;p&gt;Those are opposite outcomes. A connector that reduces both to “nothing came back” can either keep obsolete information or erase information it was never told to delete.&lt;/p&gt;

&lt;p&gt;That distinction shaped my contribution to &lt;strong&gt;Cognee issue #4766&lt;/strong&gt; for WeMakeDevs × Cognee Mergetober. After assignment, I added the Airtable connector in &lt;code&gt;cognee-community&lt;/code&gt;, without changing Cognee core. There is also an earlier Airtable proposal, &lt;strong&gt;PR #198&lt;/strong&gt;, which deserves acknowledgment. Pasted text&lt;/p&gt;

&lt;h2&gt;
  
  
  A handover question, not another export
&lt;/h2&gt;

&lt;p&gt;Consider a team keeping project owners, blockers, and decisions in Airtable. During a handover, someone asks:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;What is blocking the project handover?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;An export can answer that question once. A useful integration needs to keep the answer’s supporting information current as fields and comments change.&lt;/p&gt;

&lt;p&gt;The connector turns selected records into documents, including comments when enabled. Table schemas—the descriptions of the fields—become separate documents. Pasted text&lt;/p&gt;

&lt;p&gt;The demonstration here uses synthetic project facts, mocked Airtable responses, and deterministic model outputs, with real SQLite, Ladybug, and LanceDB storage. These are &lt;strong&gt;local verification reports, not a live Airtable deployment&lt;/strong&gt;. image(20261009-051147)&lt;/p&gt;

&lt;h2&gt;
  
  
  Change the comment. Leave the timestamp alone.
&lt;/h2&gt;

&lt;p&gt;The first capture retrieves this passage:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The blocker is AmberPermit approval.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After a comment-only edit, the retrieved passage becomes:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The blocker is TealCertificate approval.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The record timestamp stays at &lt;code&gt;2026-01-01T00:00:00Z&lt;/code&gt;. Neither distinctive fact appears in the question.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Finding the replacement is only half the check.&lt;/strong&gt; The report also checks that AmberPermit is absent from the graph and vector storage, while TealCertificate appears in retrieved passages. All nine assertions in this scenario passed. &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%2F0y66rh15o9u8r5fhxu7w.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%2F0y66rh15o9u8r5fhxu7w.png" alt=" " width="800" height="853"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Comment-only update with an unchanged timestamp. These are selected CHUNKS retrieval excerpts, not generated answers. Airtable and model responses are controlled substitutes; storage is real.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the connector reads more than it writes
&lt;/h2&gt;

&lt;p&gt;The implementation keeps per-table modified-time watermarks, but timestamps do not decide everything. Comments and schema need independent checks; computed values, blank timestamps, and restored records complicate timestamp-only filtering.&lt;/p&gt;

&lt;p&gt;The design is &lt;strong&gt;full source reconciliation with incremental document emission&lt;/strong&gt;: read the complete selected source, compare it with persisted document fingerprints, then emit changed documents and confirmed deletion markers.&lt;/p&gt;

&lt;p&gt;That means incremental writes, not an API request that fetches only changed records. Pasted text&lt;/p&gt;

&lt;p&gt;The documents themselves are normalized consistently. Field and comment ordering is deterministic. Cursor values and temporary attachment URLs are excluded, so those transport details do not become artificial content changes. Pasted text&lt;/p&gt;

&lt;p&gt;The example passes this configuration to &lt;code&gt;remember()&lt;/code&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="n"&gt;dlt_config&lt;/span&gt;&lt;span class="o"&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;primary_key&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;id&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;write_disposition&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;merge&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;max_rows_per_table&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Explicit &lt;code&gt;merge&lt;/code&gt; keeps unchanged documents in staging—the intermediate storage before Cognee processes them. Cognee’s default &lt;code&gt;replace&lt;/code&gt; would discard those unchanged staged documents. The unlimited read keeps the retained document set available for reconciliation. Pasted text&lt;/p&gt;

&lt;h2&gt;
  
  
  Two empty-looking outcomes that must disagree
&lt;/h2&gt;

&lt;p&gt;In one fixture, a selected table is emptied successfully. Its known record rows are removed, but &lt;strong&gt;three Cognee documents remain&lt;/strong&gt;: that table’s enabled schema, an independent control record, and the control table’s schema.&lt;/p&gt;

&lt;p&gt;Zero records in one table does not mean zero documents everywhere.&lt;/p&gt;

&lt;p&gt;In the contrasting fixture, an earlier table read succeeds, but a later required comments request returns &lt;strong&gt;HTTP 403&lt;/strong&gt;. The connector aborts before publishing the partial result. Existing staging, connector state, and documents remain unchanged. &lt;br&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%2Fv0w82fukl5aso8hqe8x5.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%2Fv0w82fukl5aso8hqe8x5.png" alt=" " width="800" height="635"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Two separate local scenarios: a confirmed empty table removes records; a failed required read preserves the prior state. The report records 23/23 and 14/14 passing assertions respectively.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Required reads finish before any changes are emitted. Only a complete inventory is used to identify missing records. A confirmed disappearance of a previously synchronized, still-selected table removes its records and schema. Deselecting a table, however, preserves its earlier imports. Pasted text&lt;/p&gt;

&lt;p&gt;Completing pagination is still not the same as obtaining a transactionally consistent upstream snapshot.&lt;/p&gt;
&lt;h2&gt;
  
  
  Recovery worked in specific tests. One result remains unexplained.
&lt;/h2&gt;

&lt;p&gt;Staging can succeed before Cognee stores or processes the documents. In the captured storage-failure scenario, an unchanged retry brings the staged SilverRunbook fact into document storage without another upstream edit.&lt;/p&gt;

&lt;p&gt;Cleanup has a more important caveat. With an injected deletion failure, &lt;code&gt;remember()&lt;/code&gt; reports &lt;code&gt;completed&lt;/code&gt; while stale graph content remains. The isolated cleanup retry and final chained capture passed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An earlier chained attempt did not.&lt;/strong&gt; Document/vector content remained, and the second process exited with &lt;code&gt;-11&lt;/code&gt;. The report explicitly preserves that inconsistency. A later passing run does not establish its cause or demonstrate that it has been fixed. image(20261009-051204)&lt;/p&gt;

&lt;p&gt;The supported claim is recovery in the recorded successful scenarios—not that cleanup recovery is consistently reliable. The unexplained failure still needs investigation.&lt;/p&gt;
&lt;h2&gt;
  
  
  What the local checks actually say
&lt;/h2&gt;

&lt;p&gt;The October 9 report for revision &lt;code&gt;862c98b09bbb&lt;/code&gt; records &lt;strong&gt;107 passing tests on Python 3.12, with no failures, errors, or skips&lt;/strong&gt;. Python 3.11 compatibility records 97 passes, and targeted core checks record 33.&lt;/p&gt;

&lt;p&gt;Another available regression group records 25 passes and one skip; the broader connector-regression check is marked blocked. Lint, formatting, package build, and installed-wheel smoke checks passed. Compatibility reruns are not additional unique tests.&lt;/p&gt;

&lt;p&gt;These results concern the local fixture setup. Live Airtable behavior, real-model answer quality, and hosted CI remain unverified in this report. image(20261009-051205)&lt;/p&gt;
&lt;h2&gt;
  
  
  Try the handover workflow
&lt;/h2&gt;

&lt;p&gt;Use a disposable base. Give its Personal Access Token &lt;code&gt;data.records:read&lt;/code&gt; and &lt;code&gt;schema.bases:read&lt;/code&gt;, plus &lt;code&gt;data.recordComments:read&lt;/code&gt; when comments are enabled. Pasted text&lt;/p&gt;

&lt;p&gt;Add a &lt;strong&gt;Last modified time&lt;/strong&gt; field to each selected table, tracking &lt;strong&gt;All editable fields&lt;/strong&gt; with time included. Check that setting in Airtable’s interface; the connector cannot conclusively establish it from metadata. Pasted text&lt;/p&gt;

&lt;p&gt;From &lt;code&gt;packages/connector/airtable/&lt;/code&gt; on the implementation branch, configure Cognee’s model providers as described in the README, then run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv &lt;span class="nb"&gt;sync&lt;/span&gt; &lt;span class="nt"&gt;--frozen&lt;/span&gt; &lt;span class="nt"&gt;--all-extras&lt;/span&gt; &lt;span class="nt"&gt;--python&lt;/span&gt; 3.12

&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AIRTABLE_ACCESS_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-personal-access-token"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AIRTABLE_BASE_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"appYourBaseId"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AIRTABLE_TABLE_IDS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"tblYourTableId"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;AIRTABLE_QUESTION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"What is blocking the project handover?"&lt;/span&gt;

uv run &lt;span class="nt"&gt;--frozen&lt;/span&gt; python examples/example.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add a distinctive blocker, retrieve it, edit only its comment, and run the example again. Then delete a record and check what remains. The example retrieves dataset-scoped passages; it does not generate an answer or run continuously. &lt;/p&gt;

&lt;p&gt;Full reconciliation reads unchanged records and enabled comments, so polling large tables has a cost. Select only needed tables and disable unnecessary comments. Attachments contribute stable metadata, not downloaded file contents. Pasted text Pasted text&lt;/p&gt;

&lt;p&gt;Importing the new fact is the visible part. &lt;strong&gt;Removing the old one—and knowing when not to—is the harder requirement.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>python</category>
      <category>showdev</category>
    </item>
    <item>
      <title>I connected Contentful to Cognee. Importing content was the easy part.</title>
      <dc:creator>Gauarv Chaudhary</dc:creator>
      <pubDate>Thu, 08 Oct 2026 16:41:59 +0000</pubDate>
      <link>https://dev.to/anamasgard/i-connected-contentful-to-cognee-importing-content-was-the-easy-part-387k</link>
      <guid>https://dev.to/anamasgard/i-connected-contentful-to-cognee-importing-content-was-the-easy-part-387k</guid>
      <description>&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%2Fpu5c77ipozzrko5s3lnb.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%2Fpu5c77ipozzrko5s3lnb.png" alt=" " width="800" height="420"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Built for the WeMakeDevs × Cognee Mergetober 2026.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Imagine running an online store where hundreds of products are managed in Contentful.&lt;/p&gt;

&lt;p&gt;You want an AI assistant that can answer questions about those products using Cognee's memory.&lt;/p&gt;

&lt;p&gt;Getting the content into Cognee is one problem. Keeping that memory correct when someone edits or deletes a product is another.&lt;/p&gt;

&lt;p&gt;Say a product's warranty changes from two years to one. Or the product gets discontinued entirely.&lt;/p&gt;

&lt;p&gt;If Cognee still remembers the old information, your assistant could confidently give customers the wrong answer.&lt;/p&gt;

&lt;p&gt;That was the problem behind &lt;a href="https://github.com/topoteretes/cognee/issues/4787" rel="noopener noreferrer"&gt;Cognee issue #4787&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I built a Contentful connector that imports published content into Cognee, keeps it synchronized, and removes outdated information when the original content disappears.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it actually does
&lt;/h2&gt;

&lt;p&gt;The connector lives in &lt;code&gt;cognee-community/packages/connector/contentful/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;It does five things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Connects to Contentful&lt;/strong&gt; using a Content Delivery API token.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Imports entries, asset metadata, and content models&lt;/strong&gt; as documents Cognee can understand.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Syncs changes&lt;/strong&gt; without downloading everything again.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handles deletions&lt;/strong&gt; so removed content doesn't remain in AI memory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keeps sources isolated&lt;/strong&gt; so changing one selection doesn't affect another.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Content models are especially useful here.&lt;/p&gt;

&lt;p&gt;Suppose an entry has a field called &lt;code&gt;batteryLife&lt;/code&gt; with the value &lt;code&gt;12&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Twelve what? Hours? Days?&lt;/p&gt;

&lt;p&gt;The content model provides the field's structure and description. Without that context, an AI system has less information to interpret the value correctly.&lt;/p&gt;

&lt;p&gt;So the connector imports content models separately instead of just dumping entry values into documents.&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%2Fqy63wplvpa4t4wajkryp.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%2Fqy63wplvpa4t4wajkryp.png" alt=" " width="800" height="1369"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;One synchronization run. Entries and assets use the Sync API, while content models are fetched separately.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The tricky part: knowing when to forget
&lt;/h2&gt;

&lt;p&gt;The obvious approach to synchronization is simple.&lt;/p&gt;

&lt;p&gt;Fetch everything once, then use &lt;code&gt;sys.updatedAt&lt;/code&gt; to find recently edited entries.&lt;/p&gt;

&lt;p&gt;That works for updates.&lt;/p&gt;

&lt;p&gt;But what about deletions?&lt;/p&gt;

&lt;p&gt;A deleted entry won't appear in the normal listing anymore. A timestamp filter alone can't reliably tell Cognee which information it should forget.&lt;/p&gt;

&lt;p&gt;That's why I used Contentful's native &lt;strong&gt;Sync API&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The first run imports the available content and saves a checkpoint. Later runs use that checkpoint to retrieve changes.&lt;/p&gt;

&lt;p&gt;More importantly, Contentful also returns deletion events like &lt;code&gt;DeletedEntry&lt;/code&gt; and &lt;code&gt;DeletedAsset&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The connector turns those events into dlt hard-delete markers, allowing Cognee to clean up obsolete documents and their associated graph and vector data.&lt;/p&gt;

&lt;p&gt;There's another detail I didn't want to get wrong: pagination.&lt;/p&gt;

&lt;p&gt;If Contentful returns five pages and page three fails, the connector must not save the final checkpoint.&lt;/p&gt;

&lt;p&gt;Otherwise, the next synchronization could skip changes it never processed.&lt;/p&gt;

&lt;p&gt;So it follows every page before accepting the terminal sync token. If the response is incomplete, the run fails instead of silently losing information.&lt;/p&gt;

&lt;h2&gt;
  
  
  One source shouldn't delete another source's memory
&lt;/h2&gt;

&lt;p&gt;Here's another situation.&lt;/p&gt;

&lt;p&gt;Suppose one integration imports English product descriptions, while another imports articles in multiple languages.&lt;/p&gt;

&lt;p&gt;Changing the first integration shouldn't touch the second.&lt;/p&gt;

&lt;p&gt;I added a &lt;code&gt;source_id&lt;/code&gt; to give each logical source its own synchronization state.&lt;/p&gt;

&lt;p&gt;Changing filters under the same source ID triggers a selection refresh. The connector removes documents that no longer belong to that selection while preserving other sources.&lt;/p&gt;

&lt;p&gt;Credentials are also kept separate from source identity.&lt;/p&gt;

&lt;p&gt;Rotating an API token shouldn't mean importing the entire collection as a new source.&lt;/p&gt;

&lt;h2&gt;
  
  
  The failure case I cared about
&lt;/h2&gt;

&lt;p&gt;A successful API request doesn't mean the whole synchronization succeeded.&lt;/p&gt;

&lt;p&gt;Think about this sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Contentful returns an update or deletion.&lt;/li&gt;
&lt;li&gt;dlt successfully loads the change.&lt;/li&gt;
&lt;li&gt;Cognee fails while processing or cleaning up its stored memory.&lt;/li&gt;
&lt;li&gt;The next Contentful sync returns no new changes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now what?&lt;/p&gt;

&lt;p&gt;If the connector only trusts the latest API response, that earlier failed work could remain unfinished.&lt;/p&gt;

&lt;p&gt;The implementation relies on Cognee's retained dlt staging so a later run can reconcile existing records again, even when the provider returns an empty delta.&lt;/p&gt;

&lt;p&gt;I also covered individual cleanup failures that Cognee logs without raising an exception.&lt;/p&gt;

&lt;p&gt;That's the distinction that matters here: &lt;strong&gt;successfully loading a change isn't the same as successfully updating AI memory.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Proving it works locally
&lt;/h2&gt;

&lt;p&gt;I wanted to test more than whether the connector could parse some JSON.&lt;/p&gt;

&lt;p&gt;The important questions were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does updating an entry replace obsolete content?&lt;/li&gt;
&lt;li&gt;Does deleting the final entry remove its memory?&lt;/li&gt;
&lt;li&gt;Can two sources coexist without deleting each other's data?&lt;/li&gt;
&lt;li&gt;Can an interrupted ingestion recover on the next run?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The October 8 local verification report recorded &lt;strong&gt;94 passing tests&lt;/strong&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tests&lt;/th&gt;
&lt;th&gt;Coverage&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;87&lt;/td&gt;
&lt;td&gt;Contentful provider behavior, synchronization, and dlt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Real local Cognee ingestion, storage, deletion, and recovery&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The same 94 tests passed on Python 3.11, 3.12, and 3.13, plus another Python 3.12 run with the newest permitted dependencies.&lt;/p&gt;

&lt;p&gt;Eight relevant upstream Cognee regression tests also passed, along with Ruff checks and package builds.&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%2Fi2jabsv0w22y84bpaq7o.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%2Fi2jabsv0w22y84bpaq7o.png" alt=" " width="799" height="562"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Local test evidence. Contentful responses and model operations were mocked; SQLite, Ladybug, and LanceDB storage paths were exercised for real.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That distinction is important.&lt;/p&gt;

&lt;p&gt;These tests provide evidence of local correctness, including deletion from relational, graph, and vector storage. They don't yet prove authenticated Contentful lifecycle behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to try it
&lt;/h2&gt;

&lt;p&gt;The implementation is available in my &lt;a href="https://github.com/ANAMASGARD/cognee-community/tree/feat/4787-contentful-connector/packages/connector/contentful" rel="noopener noreferrer"&gt;Contentful connector branch&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;From the connector directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv &lt;span class="nb"&gt;sync&lt;/span&gt; &lt;span class="nt"&gt;--locked&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configure your Contentful space and Delivery token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;CONTENTFUL_SPACE_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-space-id"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;CONTENTFUL_DELIVERY_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-token"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;CONTENTFUL_ENVIRONMENT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"master"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;LLM_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-model-key"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv run python examples/example.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The example synchronizes published content into Cognee and runs a search query.&lt;/p&gt;

&lt;p&gt;You can select specific content types and languages, or disable asset ingestion. Subsequent runs synchronize changes for the same source.&lt;/p&gt;

&lt;p&gt;For example, a product-catalog application could import descriptions, search for products matching a customer's requirements, and refresh its memory after catalog edits.&lt;/p&gt;

&lt;p&gt;That's an illustrative use case. A live Contentful-to-Cognee demonstration is still needed to establish real-provider behavior.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it doesn't do
&lt;/h2&gt;

&lt;p&gt;A few limitations worth mentioning:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It reads published content, not drafts.&lt;/li&gt;
&lt;li&gt;It imports asset metadata, not image or video contents.&lt;/li&gt;
&lt;li&gt;It doesn't recursively expand every linked entry.&lt;/li&gt;
&lt;li&gt;It doesn't include a built-in scheduler.&lt;/li&gt;
&lt;li&gt;Authenticated Contentful import, update, unpublish, and deletion are not yet live-verified.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An invalid synchronization checkpoint also requires deliberate recovery rather than an automatic destructive reset.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd take away from this
&lt;/h2&gt;

&lt;p&gt;Importing data once is straightforward.&lt;/p&gt;

&lt;p&gt;Keeping it correct when the source changes, an entry disappears, or processing fails halfway through is the harder problem.&lt;/p&gt;

&lt;p&gt;The useful lesson from this connector is that synchronization needs to account for both sides: what the source says changed, and what the destination actually finished processing.&lt;/p&gt;

&lt;p&gt;Otherwise, everything can appear to work while the AI is still remembering yesterday's information.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Code and references&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/topoteretes/cognee/issues/4787" rel="noopener noreferrer"&gt;Cognee issue #4787&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ANAMASGARD/cognee-community/tree/feat/4787-contentful-connector/packages/connector/contentful" rel="noopener noreferrer"&gt;Contentful connector implementation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/ANAMASGARD/cognee-community/blob/feat/4787-contentful-connector/packages/connector/contentful/tests/test_contentful_ingestion.py" rel="noopener noreferrer"&gt;Integration and recovery tests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.contentful.com/developers/docs/concepts/sync/" rel="noopener noreferrer"&gt;Contentful Sync API documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pull request:&lt;/strong&gt; &lt;a href="https://github.com/topoteretes/cognee-community/pull/308" rel="noopener noreferrer"&gt;https://github.com/topoteretes/cognee-community/pull/308&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Built as part of WeMakeDevs × Cognee Mergetober 2026.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>cognee</category>
    </item>
    <item>
      <title>I built a OneNote connector for Cognee. Importing notes was the easy part.</title>
      <dc:creator>Gauarv Chaudhary</dc:creator>
      <pubDate>Thu, 08 Oct 2026 11:19:46 +0000</pubDate>
      <link>https://dev.to/anamasgard/i-built-a-onenote-connector-for-cognee-importing-notes-was-the-easy-part-19np</link>
      <guid>https://dev.to/anamasgard/i-built-a-onenote-connector-for-cognee-importing-notes-was-the-easy-part-19np</guid>
      <description>&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%2Fzc550hhhyrh1lttvhjp3.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%2Fzc550hhhyrh1lttvhjp3.png" alt=" " width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Built for the Cognee community connector hackathon → &lt;a href="https://github.com/topoteretes/cognee/issues/4727" rel="noopener noreferrer"&gt;issue #4727&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Say you're working on a project. You've got architecture decisions in one OneNote section, meeting notes in another, and a few pages explaining why you abandoned certain approaches.&lt;/p&gt;

&lt;p&gt;Two months later, someone asks a simple question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Why did we choose this approach instead of the other one?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You know you wrote it down somewhere.&lt;/p&gt;

&lt;p&gt;Now you have to remember which notebook it was in, which section you used, and what you called the page.&lt;/p&gt;

&lt;p&gt;That's the situation I had in mind when I picked up &lt;a href="https://github.com/topoteretes/cognee/issues/4727" rel="noopener noreferrer"&gt;Cognee issue #4727&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;OneNote already does a good job of storing and organizing notes. I wasn't trying to replace it. I wanted to make those notes available to Cognee, so someone could ask a question and retrieve information from the pages they'd already written.&lt;/p&gt;

&lt;p&gt;But there was another problem I hadn't wanted to ignore.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happens when those notes change?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If someone edits a decision, moves a page, or deletes something entirely, the searchable memory shouldn't keep treating the old version as the truth.&lt;/p&gt;

&lt;p&gt;That turned out to be the more interesting part of the implementation.&lt;/p&gt;

&lt;p&gt;Here's what I built.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code:&lt;/strong&gt; &lt;a href="https://github.com/topoteretes/cognee-community/pull/301" rel="noopener noreferrer"&gt;OneNote connector implementation&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What it actually does
&lt;/h2&gt;

&lt;p&gt;The connector lets you select specific OneNote notebooks and bring their content into Cognee's document-memory pipeline.&lt;/p&gt;

&lt;p&gt;The workflow looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Sign in using Microsoft Graph's delegated authentication.&lt;/li&gt;
&lt;li&gt;Choose the notebooks you want to use.&lt;/li&gt;
&lt;li&gt;The connector walks through notebooks, sections, nested section groups, and pages.&lt;/li&gt;
&lt;li&gt;It converts the page HTML into readable text while preserving notebook and section context.&lt;/li&gt;
&lt;li&gt;Cognee processes the documents so their content can be retrieved later.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The important part is that &lt;strong&gt;you choose what gets ingested&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;You don't have to import every notebook in your account just because you connected Microsoft.&lt;/p&gt;

&lt;p&gt;And the context matters.&lt;/p&gt;

&lt;p&gt;Imagine two pages both called "Database."&lt;/p&gt;

&lt;p&gt;One is inside &lt;code&gt;Prototype / Experiments&lt;/code&gt;. The other is inside &lt;code&gt;Production / Architecture&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Without that context, they're just two documents with the same title. The connector includes the notebook and section path in the searchable text, so the original location isn't lost.&lt;/p&gt;

&lt;p&gt;It also preserves existing image alt text and resource references. It doesn't download binary images or attachments.&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%2Fjafcwi9kl5kc38fhwo8b.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%2Fjafcwi9kl5kc38fhwo8b.png" alt="Architecture diagram showing selected OneNote notebooks passing through Microsoft Graph, document processing, Cognee memory, and retrieval, with separate handling for synchronization and deletion." width="800" height="755"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting it running
&lt;/h2&gt;

&lt;p&gt;I kept this as a Python connector rather than building another dashboard.&lt;/p&gt;

&lt;p&gt;That's also what the issue asked for: a package, documentation, a runnable example, and tests. No new frontend was needed.&lt;/p&gt;

&lt;p&gt;The implementation lives in &lt;code&gt;packages/connector/onenote/&lt;/code&gt; in &lt;a href="https://github.com/topoteretes/cognee-community" rel="noopener noreferrer"&gt;cognee-community&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With Python 3.11–3.13 and &lt;code&gt;uv&lt;/code&gt;, the setup starts with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;packages/connector/onenote
uv &lt;span class="nb"&gt;sync&lt;/span&gt; &lt;span class="nt"&gt;--locked&lt;/span&gt; &lt;span class="nt"&gt;--group&lt;/span&gt; dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Connecting your Microsoft account
&lt;/h3&gt;

&lt;p&gt;There's one detail worth explaining here.&lt;/p&gt;

&lt;p&gt;Microsoft's OneNote API doesn't support app-only authentication. The connector needs delegated access, meaning it works on behalf of a signed-in user.&lt;/p&gt;

&lt;p&gt;The example uses MSAL's device-code login flow.&lt;/p&gt;

&lt;p&gt;You register an application in the &lt;a href="https://entra.microsoft.com/" rel="noopener noreferrer"&gt;Microsoft Entra admin center&lt;/a&gt;, enable public-client authentication, and add the delegated &lt;code&gt;Notes.Read&lt;/code&gt; permission. Depending on your organization's settings, administrator consent might be required.&lt;/p&gt;

&lt;p&gt;Microsoft documents the authentication requirement &lt;a href="https://learn.microsoft.com/en-us/graph/api/resources/onenote-api-overview?view=graph-rest-1.0" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;After registering the app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;MICROSOFT_CLIENT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-application-id"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;MICROSOFT_TENANT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"common"&lt;/span&gt;

uv run &lt;span class="nt"&gt;--frozen&lt;/span&gt; python examples/onenote_example.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first run is designed to guide you through authentication and list the notebooks available to your account.&lt;/p&gt;

&lt;p&gt;Then you choose one or more notebook IDs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ONENOTE_NOTEBOOK_IDS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-notebook-id"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ONENOTE_DATASET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"onenote-project-demo"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;LLM_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your-provider-key"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ONENOTE_QUESTION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Why did we choose this project approach?"&lt;/span&gt;

uv run &lt;span class="nt"&gt;--frozen&lt;/span&gt; python examples/onenote_example.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll also need to configure Cognee's LLM and embedding providers appropriately.&lt;/p&gt;

&lt;p&gt;The example is designed to ingest the selected notebook, process its documents, and run the question.&lt;/p&gt;

&lt;p&gt;For a first experiment, I'd use a disposable notebook with a decision and its explanation, rather than importing personal notes immediately.&lt;/p&gt;

&lt;p&gt;For example, a hypothetical page might say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;We chose local storage for the first prototype because users needed to work without an internet connection.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Then ask:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Why did we choose local storage?"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The result should be grounded in that page, not in a generic explanation of storage options.&lt;/p&gt;

&lt;p&gt;This is an example of the workflow, not a result from a completed live Microsoft test.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part that needed more thought: synchronization
&lt;/h2&gt;

&lt;p&gt;Importing documents once is only half the job.&lt;/p&gt;

&lt;p&gt;Suppose you wrote that your project uses SQLite. A week later, you switch to PostgreSQL and update the explanation in OneNote.&lt;/p&gt;

&lt;p&gt;If Cognee still retrieves the old explanation, having searchable notes hasn't really solved your problem.&lt;/p&gt;

&lt;p&gt;So I needed the connector to handle repeated synchronization.&lt;/p&gt;

&lt;p&gt;Here's how that works.&lt;/p&gt;

&lt;p&gt;Every sync checks the metadata of the selected notebooks. Each OneNote page has a &lt;code&gt;lastModifiedDateTime&lt;/code&gt; value, which helps determine whether its content needs to be fetched again.&lt;/p&gt;

&lt;p&gt;If the page hasn't changed, the connector can reuse its cached body.&lt;/p&gt;

&lt;p&gt;If it's new or modified, the connector fetches its HTML again.&lt;/p&gt;

&lt;p&gt;One distinction matters: &lt;strong&gt;this is incremental content fetching, not a delta-only metadata feed.&lt;/strong&gt; The connector still enumerates the selected notebooks to determine what's present.&lt;/p&gt;

&lt;p&gt;There's another edge case.&lt;/p&gt;

&lt;p&gt;What if you rename a section without changing the page itself?&lt;/p&gt;

&lt;p&gt;The page text might be identical, but its location has changed.&lt;/p&gt;

&lt;p&gt;So the connector caches the page body separately and rebuilds the notebook and section context from current metadata.&lt;/p&gt;

&lt;p&gt;That way, a rename doesn't require downloading an unchanged page body just to update its location.&lt;/p&gt;

&lt;p&gt;And if a timestamp changes without changing the actual emitted document, the document identity can stay the same.&lt;/p&gt;

&lt;p&gt;Less unnecessary processing, while keeping the stored context up to date.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deleting a page is where things get risky
&lt;/h2&gt;

&lt;p&gt;This was the part I wanted to get right.&lt;/p&gt;

&lt;p&gt;Imagine Cognee has already ingested 50 pages from a notebook.&lt;/p&gt;

&lt;p&gt;On the next sync, Microsoft Graph returns only 30 because a request failed partway through pagination.&lt;/p&gt;

&lt;p&gt;If the connector blindly assumes the missing 20 pages were deleted, it could remove perfectly valid information.&lt;/p&gt;

&lt;p&gt;That's a pretty bad outcome for something that's supposed to help you remember things.&lt;/p&gt;

&lt;p&gt;So the connector doesn't treat a missing listing entry as proof of deletion.&lt;/p&gt;

&lt;p&gt;It checks previously known missing pages directly and considers the API's error responses and the surrounding notebook context.&lt;/p&gt;

&lt;p&gt;Microsoft has &lt;a href="https://learn.microsoft.com/en-us/graph/onenote-error-codes" rel="noopener noreferrer"&gt;different OneNote error codes&lt;/a&gt; for deleted resources, nonexistent resources, invalid identifiers, and access problems.&lt;/p&gt;

&lt;p&gt;Those differences matter.&lt;/p&gt;

&lt;p&gt;A confirmed deletion can lead to removing obsolete memory after successful reconciliation.&lt;/p&gt;

&lt;p&gt;A permission failure or ambiguous response shouldn't.&lt;/p&gt;

&lt;p&gt;The connector also handles a case that's easy to overlook:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What happens when you delete the very last page?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;An empty Python generator isn't necessarily enough to make a loading system replace an existing table with an empty one.&lt;/p&gt;

&lt;p&gt;For the final-page case, the implementation uses DLT's empty-table materialization marker:&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;if&lt;/span&gt; &lt;span class="n"&gt;notebook_rows&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;notebook_rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;dlt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mark&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;materialize_table_schema&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That explicitly represents an empty notebook table instead of creating a fake blank document.&lt;/p&gt;

&lt;p&gt;There are also deliberate boundaries.&lt;/p&gt;

&lt;p&gt;Deselecting a notebook doesn't automatically delete its previous memory. A page moved outside the selected scope retains its earlier ingested copy with a warning.&lt;/p&gt;

&lt;p&gt;Those choices may sound conservative, but I'd rather have a connector report uncertainty than silently delete data because an API request failed.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I actually tested
&lt;/h2&gt;

&lt;p&gt;I didn't want to stop after checking whether the connector could parse a page.&lt;/p&gt;

&lt;p&gt;The important cases were the ones where something changed or went wrong.&lt;/p&gt;

&lt;p&gt;For local verification, I tested the connector using mocked Microsoft Graph responses and mocked external model calls.&lt;/p&gt;

&lt;p&gt;The storage layer wasn't mocked.&lt;/p&gt;

&lt;p&gt;Five integration tests exercised real SQLite, Ladybug, and LanceDB persistence and retrieval.&lt;/p&gt;

&lt;p&gt;The tests covered things like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deleting the final page and checking that its owned stored artifacts disappear.&lt;/li&gt;
&lt;li&gt;Updating a document without retaining the obsolete version.&lt;/li&gt;
&lt;li&gt;Recovering from a processing failure on a later sync.&lt;/li&gt;
&lt;li&gt;Retrying cleanup after an earlier failure.&lt;/li&gt;
&lt;li&gt;Keeping shared graph information that still belongs to another document.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;66 package tests passed locally on Python 3.12.&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%2Flk6wh4d26hruvrookz7e.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%2Flk6wh4d26hruvrookz7e.png" alt="Python pytest output showing 66 OneNote connector tests passing locally, using mocked Microsoft Graph and model calls with real SQLite, Ladybug, and LanceDB storage." width="800" height="571"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Another &lt;strong&gt;24 nearby Cognee regression tests&lt;/strong&gt; also passed, along with lint, formatting, package-build, and wheel-import checks.&lt;/p&gt;

&lt;p&gt;The five persistence integration tests also passed separately.&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%2F7bi8lkbc3buqvehhk1ws.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%2F7bi8lkbc3buqvehhk1ws.png" alt="Terminal test results showing five passing Cognee integration tests for final-page deletion, content updates, failure recovery, cleanup retries, and preservation of shared graph data." width="800" height="707"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The package suite can be run with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv run &lt;span class="nt"&gt;--frozen&lt;/span&gt; pytest tests &lt;span class="nt"&gt;-ra&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The captured local test runs also reported warnings, so the passing summaries shouldn't be read as proof that every possible environment is free of issues.&lt;/p&gt;

&lt;p&gt;And one limitation is important enough to say separately:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I haven't completed live Microsoft validation yet.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That means I haven't demonstrated the full sign-in, notebook selection, edit, and deletion workflow against a real Microsoft account.&lt;/p&gt;

&lt;p&gt;The tests exercise the behavior with controlled Graph responses and real local storage. They don't prove that Microsoft's live API will behave exactly like the mocks.&lt;/p&gt;

&lt;p&gt;Hosted GitHub Actions CI validation is also still pending in this report.&lt;/p&gt;

&lt;p&gt;I don't want to turn a successful local test run into a claim about something I haven't tested.&lt;/p&gt;

&lt;h2&gt;
  
  
  Things it doesn't do
&lt;/h2&gt;

&lt;p&gt;A few boundaries are worth knowing before using this on important notes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;There's no dedicated OneNote dashboard.&lt;/strong&gt; This contribution is a Python data-source connector. Authentication and notebook selection are handled through the example and API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It doesn't download attachments.&lt;/strong&gt; Existing image descriptions and resource references are preserved, but binary attachments aren't ingested.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It doesn't perform a delta-only metadata sync.&lt;/strong&gt; Each run enumerates the selected scope, while unchanged page bodies can come from the cache.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Processing isn't one atomic transaction.&lt;/strong&gt; Snapshot validation, staging, document ingestion, graph processing, and cleanup happen in separate stages. If later processing fails, rerunning the same selection is the recovery path.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It needs persistent state.&lt;/strong&gt; Cached notebook bodies and sync state are stored locally. That state can contain sensitive notes and should be protected.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Concurrent syncs are limited.&lt;/strong&gt; The current supported usage is one active sync per account and dataset scope.&lt;/p&gt;

&lt;p&gt;These aren't things I'd hide behind a nice screenshot. They're part of knowing what the connector can safely do.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd take away from building this
&lt;/h2&gt;

&lt;p&gt;The issue looked like a straightforward integration task at first: authenticate, fetch pages, convert HTML, send documents to Cognee.&lt;/p&gt;

&lt;p&gt;But a useful connector has to answer a harder question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When the source changes, what should happen to the information we've already stored?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Fetching a page is one operation.&lt;/p&gt;

&lt;p&gt;Knowing when to reuse it, update it, or safely forget it requires a lot more care.&lt;/p&gt;

&lt;p&gt;That's what most of this contribution ended up focusing on.&lt;/p&gt;

&lt;p&gt;The implementation is available in the community repository, with local tests passing and live Microsoft validation still to be completed.&lt;/p&gt;

&lt;p&gt;If you're interested in how it works, the code and tests are here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repository:&lt;/strong&gt; &lt;a href="https://github.com/topoteretes/cognee-community" rel="noopener noreferrer"&gt;https://github.com/topoteretes/cognee-community&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Implementation:&lt;/strong&gt; &lt;a href="https://github.com/ANAMASGARD/cognee-community/tree/feat/4727-onenote-connector/packages/connector/onenote" rel="noopener noreferrer"&gt;https://github.com/ANAMASGARD/cognee-community/tree/feat/4727-onenote-connector/packages/connector/onenote&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Issue:&lt;/strong&gt; &lt;a href="https://github.com/topoteretes/cognee/issues/4727" rel="noopener noreferrer"&gt;https://github.com/topoteretes/cognee/issues/4727&lt;/a&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>python</category>
      <category>showdev</category>
      <category>cognee</category>
    </item>
  </channel>
</rss>
