<?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: Priyansh Narang</title>
    <description>The latest articles on DEV Community by Priyansh Narang (@priyansh_narang_c1b96ab75).</description>
    <link>https://dev.to/priyansh_narang_c1b96ab75</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%2F1565492%2F0d3155b4-6ab3-430e-b484-205ba855cc1c.jpg</url>
      <title>DEV Community: Priyansh Narang</title>
      <link>https://dev.to/priyansh_narang_c1b96ab75</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/priyansh_narang_c1b96ab75"/>
    <language>en</language>
    <item>
      <title>Teaching an AI agent what’s blocking the sprint: a ClickUp connector for cognee</title>
      <dc:creator>Priyansh Narang</dc:creator>
      <pubDate>Thu, 15 Oct 2026 04:03:36 +0000</pubDate>
      <link>https://dev.to/priyansh_narang_c1b96ab75/teaching-an-ai-agent-whats-blocking-the-sprint-a-clickup-connector-for-cognee-2k73</link>
      <guid>https://dev.to/priyansh_narang_c1b96ab75/teaching-an-ai-agent-whats-blocking-the-sprint-a-clickup-connector-for-cognee-2k73</guid>
      <description>&lt;p&gt;Most teams have the same problem: the answer to "why is this blocked?" exists, but it's spread across a task status, a custom field, three comments and a Doc someone wrote last month. Nobody reads all of that before stand-up.&lt;/p&gt;

&lt;p&gt;I wanted an agent that could, so I built a ClickUp connector for &lt;a href="https://github.com/topoteretes/cognee" rel="noopener noreferrer"&gt;cognee&lt;/a&gt;, the open-source memory layer for AI agents, as part of Mergetober. It turns tasks, comments and Docs into memory an agent can query, and keeps that memory in sync as the workspace changes.&lt;/p&gt;

&lt;p&gt;This post is about what it took to make that reliable. The ClickUp API had a few surprises, and two of them only showed up when I tested against a real workspace.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ten lines to a sprint memory
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;cognee&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;cognee_community_connector_clickup&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;clickup_source&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;cognee&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remember&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;clickup_source&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;space_ids&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;90120001&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;  &lt;span class="c1"&gt;# token from CLICKUP_API_TOKEN
&lt;/span&gt;        &lt;span class="n"&gt;dataset_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sprint_memory&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&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="n"&gt;write_disposition&lt;/span&gt;&lt;span class="o"&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="n"&gt;max_rows_per_table&lt;/span&gt;&lt;span class="o"&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;span class="n"&gt;answer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;cognee&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;query_text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Which tasks are blocked, and what are they waiting on?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;query_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;cognee&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SearchType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GRAPH_COMPLETION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;datasets&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;sprint_memory&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Authentication is a ClickUp personal API token (avatar → Settings → Apps → API Token → Generate). You choose what to ingest with &lt;code&gt;space_ids&lt;/code&gt;, &lt;code&gt;folder_ids&lt;/code&gt; or &lt;code&gt;list_ids&lt;/code&gt;. Run it again tomorrow and only what changed gets fetched.&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%2Fk1p7921t91cnpb64bh7s.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%2Fk1p7921t91cnpb64bh7s.png" alt="How the connector works: connect, list tasks, enrich, docs and cognify, with a forget-on-delete check on every run" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The catch with ClickUp's hierarchy
&lt;/h2&gt;

&lt;p&gt;ClickUp nests everything: Workspace → Space → Folder → List → Task, and each level is its own API call. The obvious implementation walks the whole tree before it sees a single task, and does it again on every sync. ClickUp allows 100 requests per minute, so on a large workspace that walk is most of your budget.&lt;/p&gt;

&lt;p&gt;The way around it is the filtered team-tasks endpoint, &lt;code&gt;GET /team/{team_id}/task&lt;/code&gt;. It returns tasks from every list in the workspace, 100 per page, and accepts a &lt;code&gt;date_updated_gt&lt;/code&gt; filter. Each task already carries its list and folder names. The only thing it doesn't name is the Space, so the connector fetches Space names once, caches them in the dlt state, and refreshes them only when a task points at a Space it hasn't seen.&lt;/p&gt;

&lt;p&gt;On my test workspace that meant &lt;strong&gt;one&lt;/strong&gt; Space lookup on the first sync and &lt;strong&gt;zero&lt;/strong&gt; on every sync after.&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%2Fxfzjmjxgxw4mnck8r9av.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%2Fxfzjmjxgxw4mnck8r9av.png" alt="Walking every level of the ClickUp hierarchy on each sync, compared with reading tasks workspace-wide and caching only Space names" width="799" height="413"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Custom fields don't return what you see
&lt;/h2&gt;

&lt;p&gt;Custom fields are where teams keep the important stuff: severity, components, the reviewer. But the API returns them in a form only ClickUp understands. A dropdown comes back as &lt;code&gt;2&lt;/code&gt;, labels as a list of option IDs, and a people field as a list of user objects.&lt;/p&gt;

&lt;p&gt;Feed that straight into a knowledge graph and the agent learns that "Severity is 2". The connector resolves each value through the field's &lt;code&gt;type_config&lt;/code&gt;, so the document says "Severity: High". It also handles dates, checkboxes, currency, ratings, locations and progress. Here's the connector's real output for an example task:&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%2Fv4bf6wr74iqr15iowooi.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%2Fv4bf6wr74iqr15iowooi.png" alt="Raw ClickUp custom-field values next to the Markdown document the connector stores, with the values decoded into readable labels" width="800" height="508"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Each task document also carries status, priority, assignees, tags, its Space / Folder / List location, checklists and &lt;strong&gt;every&lt;/strong&gt; comment. The comment endpoint returns only the 25 newest, so the connector pages back through the rest. ClickUp Docs are synced too, through the v3 Docs API, with nested pages rendered as sections.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two things I only found on a real workspace
&lt;/h2&gt;

&lt;p&gt;My test suite passed. Then I pointed the connector at a real ClickUp workspace, and two assumptions turned out to be wrong.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. &lt;code&gt;date_updated_gt&lt;/code&gt; isn't strictly "greater than".&lt;/strong&gt; I passed the newest task's own timestamp, and ClickUp still returned that task (newest − 1 ms returned it, newest returned it, newest + 1 ms didn't). So every "nothing changed" rerun was re-ingesting the latest task and re-fetching its comments. The fix: skip a task whose timestamp equals the cursor if it's already been ingested, while still picking up a genuinely new task created in that same millisecond.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Deleting a comment is invisible.&lt;/strong&gt; I posted a test comment on a real task and watched the task's &lt;code&gt;date_updated&lt;/code&gt;. It moved, so new comments are picked up. Then I deleted the comment, and &lt;code&gt;date_updated&lt;/code&gt; stayed exactly the same. A cursor-based sync would have kept that deleted comment in the agent's memory forever.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Action in ClickUp&lt;/th&gt;
&lt;th&gt;Changes &lt;code&gt;date_updated&lt;/code&gt;?&lt;/th&gt;
&lt;th&gt;How the connector handles it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Add a comment&lt;/td&gt;
&lt;td&gt;Yes (verified live)&lt;/td&gt;
&lt;td&gt;Next incremental sync&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Delete a comment&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;No&lt;/strong&gt; (verified live)&lt;/td&gt;
&lt;td&gt;Daily comment re-check&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The fix is a comment re-check, once a day by default. The connector re-reads comments on the tasks in scope, compares them with a fingerprint it stored last time, and re-renders only the tasks whose comments changed. I verified it against the live workspace too: the deleted comment was gone from memory after the next check.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a sync costs
&lt;/h2&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%2Ftcqu5uq2wr4l8i5zzh17.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%2Ftcqu5uq2wr4l8i5zzh17.png" alt="Requests per run: 17 on the first sync, 4 on a rerun with no changes, 16 on the daily comment re-check" width="800" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;These numbers are from my test workspace (12 tasks, 1 Doc). The first sync made 17 requests, most of them comments, one per task. A rerun with nothing changed made 4: the workspace lookup, the incremental listing, the deletion check and the Docs listing. The daily comment re-check costs about one request per task, which is why it runs daily rather than on every sync.&lt;/p&gt;

&lt;h2&gt;
  
  
  Forgetting is part of memory
&lt;/h2&gt;

&lt;p&gt;ClickUp has no deletion feed, so every run re-lists the IDs in scope. Tasks and Docs that were deleted, archived or moved out of the selection become tombstone rows (&lt;code&gt;{"id": ..., "_deleted": true}&lt;/code&gt;). dlt hard-deletes them, and cognee's orphan cleanup removes them from the graph and vector store.&lt;/p&gt;

&lt;p&gt;Two guards stop a bad day from wiping memory. If a listing comes back empty while items are known, the connector assumes an outage and skips the cleanup. And it only saves its progress after a run fully succeeds, so a crash halfway through just means the next run repeats the work.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Threaded replies&lt;/strong&gt; to comments aren't fetched; ClickUp serves them through a separate endpoint per comment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Attachments&lt;/strong&gt; aren't downloaded.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Docs are scoped by their direct parent&lt;/strong&gt;: selecting a Space includes Docs that live directly in it.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Mocked tests told me the code did what I expected. Only the real API told me whether my expectations were right. Both of the bugs above passed every unit test, because the fake API behaved the way I assumed ClickUp did. Now the fake behaves the way ClickUp actually does, so the tests guard against those bugs coming back.&lt;/p&gt;

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

&lt;p&gt;The connector is in review as &lt;a href="https://github.com/topoteretes/cognee-community/pull/278" rel="noopener noreferrer"&gt;topoteretes/cognee-community#278&lt;/a&gt;, for &lt;a href="https://github.com/topoteretes/cognee/issues/4701" rel="noopener noreferrer"&gt;issue #4701&lt;/a&gt;. Until it's merged, you can install it from my branch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="s2"&gt;"cognee-community-connector-clickup @ git+https://github.com/priyansh-narang2308/cognee-community@feature/clickup-connector#subdirectory=packages/connector/clickup"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your team lives in ClickUp, I'd like to hear what you'd ask a sprint agent first.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>cognee</category>
      <category>memory</category>
      <category>programming</category>
    </item>
    <item>
      <title>I gave an AI assistant a memory of PubMed - and learned why "forgetting" is the hard part</title>
      <dc:creator>Priyansh Narang</dc:creator>
      <pubDate>Thu, 15 Oct 2026 03:51:03 +0000</pubDate>
      <link>https://dev.to/priyansh_narang_c1b96ab75/i-gave-an-ai-assistant-a-memory-of-pubmed-and-learned-why-forgetting-is-the-hard-part-10fj</link>
      <guid>https://dev.to/priyansh_narang_c1b96ab75/i-gave-an-ai-assistant-a-memory-of-pubmed-and-learned-why-forgetting-is-the-hard-part-10fj</guid>
      <description>&lt;p&gt;I wanted to see whether an AI assistant could keep up with a fast-moving corner of cancer research instead of answering from whatever it saw during training. So I picked one narrow question: &lt;strong&gt;how do KRAS G12C lung and colorectal tumours become resistant to sotorasib and adagrasib?&lt;/strong&gt; Then I built a PubMed connector for &lt;a href="https://github.com/topoteretes/cognee" rel="noopener noreferrer"&gt;cognee&lt;/a&gt;, the open-source memory layer for AI agents, as part of Mergetober.&lt;/p&gt;

&lt;p&gt;This post covers how to use it, what actually happened when I pointed it at real PubMed data, and three things about NCBI's API that surprised me.&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%2Fnftsjtnootkcq4xyiby2.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%2Fnftsjtnootkcq4xyiby2.png" alt=" " width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with "just ask the model"
&lt;/h2&gt;

&lt;p&gt;Resistance to KRAS G12C inhibitors is an active research area, with new case reports and preprints every month. A model's training data is frozen. Pasting a few abstracts into a chat window doesn't scale and is gone the next session.&lt;/p&gt;

&lt;p&gt;What I wanted was &lt;strong&gt;memory&lt;/strong&gt;: a store that pulls in the relevant literature, keeps it up to date on its own, and turns it into something an agent can reason over (entities and relationships, not just text chunks). That's what cognee does with documents. It was missing a way to get PubMed into it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ten lines to a literature memory
&lt;/h2&gt;

&lt;p&gt;The connector is a &lt;code&gt;dlt&lt;/code&gt; source you hand to &lt;code&gt;cognee.remember&lt;/code&gt;. You choose what to ingest with an ordinary PubMed query, exactly what you'd type into the PubMed search box.&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;cognee&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;cognee_community_connector_pubmed&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pubmed_source&lt;/span&gt;

&lt;span class="n"&gt;TERM&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'"&lt;/span&gt;&lt;span class="s"&gt;KRAS G12C&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; AND (adagrasib OR sotorasib) AND resistance&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;


&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;cognee&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;remember&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nf"&gt;pubmed_source&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;term&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;TERM&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mindate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2024/01/01&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;dataset_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kras_g12c_resistance&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;primary_key&lt;/span&gt;&lt;span class="o"&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="n"&gt;write_disposition&lt;/span&gt;&lt;span class="o"&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="n"&gt;max_rows_per_table&lt;/span&gt;&lt;span class="o"&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;span class="n"&gt;answer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;cognee&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;query_text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Which secondary KRAS mutations cause resistance to G12C inhibitors?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;query_type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;cognee&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SearchType&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GRAPH_COMPLETION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;datasets&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;kras_g12c_resistance&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No API key is required, because PubMed is public. If you create a free NCBI key, the rate limit goes from 3 to 10 requests per second:&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;NCBI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"your_key"&lt;/span&gt;      &lt;span class="c"&gt;# account.ncbi.nlm.nih.gov → Account settings → API Key Management&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;NCBI_EMAIL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"you@example.org"&lt;/span&gt; &lt;span class="c"&gt;# NCBI asks tools to send a contact address&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What happened on real data
&lt;/h2&gt;

&lt;p&gt;Before involving an LLM at all, I wanted to see the ingest side behave, so I ran the sync twice against live NCBI and counted the requests.&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%2Fku1frpfk11tiv5fkuwbi.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%2Fku1frpfk11tiv5fkuwbi.png" alt=" " width="800" height="322"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The query matches &lt;strong&gt;253&lt;/strong&gt; PubMed records in total. Limiting it to records added since 2024 left &lt;strong&gt;155&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The first sync stored all 155 in about &lt;strong&gt;7 seconds&lt;/strong&gt;, using just two EFetch batches.&lt;/li&gt;
&lt;li&gt;The second sync made &lt;strong&gt;zero&lt;/strong&gt; EFetch calls and finished in about 2.4 seconds. Nothing had changed, so nothing was downloaded or sent to the LLM again.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;36&lt;/strong&gt; of the 155 had structured abstracts. The connector keeps those sections (Background, Methods, Results, Conclusions) as headings, which gives cognify much cleaner text to work with.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Three of the papers mention &lt;strong&gt;Y96&lt;/strong&gt;, a mutation in the switch-II pocket where these drugs bind. One of them, a 2026 case report in &lt;em&gt;The Oncologist&lt;/em&gt; (&lt;a href="https://pubmed.ncbi.nlm.nih.gov/42474245/" rel="noopener noreferrer"&gt;PMID 42474245&lt;/a&gt;), describes a patient whose tumour developed both Y96S and Y96C after about nine months on a G12C inhibitor. Adding cetuximab and trametinib to sotorasib didn't hold it back; progression-free survival was 1.9 months.&lt;/p&gt;

&lt;p&gt;This is the kind of finding I want an assistant to remember and connect: drug → target → resistance mutation → which combinations didn't work. A frozen model can't know about a paper from this summer, but the memory layer now does.&lt;/p&gt;

&lt;h2&gt;
  
  
  Under the hood
&lt;/h2&gt;

&lt;p&gt;E-utilities is a two-step API. &lt;code&gt;esearch.fcgi&lt;/code&gt; turns a query into PMIDs, and &lt;code&gt;efetch.fcgi&lt;/code&gt; returns the full XML for up to 200 of them at a time. The connector parses that XML into Markdown (title, authors, journal, DOI and PMCID, labelled abstract sections, MeSH terms) and writes one row per article. cognee then ingests each row as a document and runs entity extraction on it.&lt;/p&gt;

&lt;p&gt;Getting that basic loop working took an afternoon. Making it correct took longer, because of these three surprises.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. ESearch stops at 10,000 results
&lt;/h3&gt;

&lt;p&gt;ESearch never returns more than 10,000 PMIDs for a query. My first test, plain &lt;code&gt;CRISPR gene therapy&lt;/code&gt;, matches &lt;strong&gt;10,930&lt;/strong&gt; records. A naive loop would silently drop the last 930.&lt;/p&gt;

&lt;p&gt;The fix: if a date window has more than 10,000 hits, split it in half and search each half, repeating until every slice fits. On the CRISPR query that took 13 requests and listed all 10,930 records with no duplicates.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Entrez dates are in US Eastern time
&lt;/h3&gt;

&lt;p&gt;Incremental sync uses the Entrez date (&lt;code&gt;edat&lt;/code&gt;), the day a record entered PubMed. After each run the connector saves the last day it covered and searches from there next time.&lt;/p&gt;

&lt;p&gt;NCBI assigns those dates in US Eastern time, though, so a run just after midnight UTC can miss records added late on the previous Eastern day. The connector re-reads one day of overlap and skips PMIDs it already has, so the overlap costs one extra search rather than re-ingesting anything.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. A retraction doesn't change the date
&lt;/h3&gt;

&lt;p&gt;A memory layer has to be able to forget, especially in medicine. If a paper is retracted, you don't want your assistant citing it next month.&lt;/p&gt;

&lt;p&gt;But retracting a paper doesn't change its Entrez date, so a date-based cursor would never notice. Each run also searches &lt;code&gt;(your query) AND retracted publication[pt]&lt;/code&gt; and removes any article you already ingested that has since been retracted.&lt;/p&gt;

&lt;p&gt;Deleted records work the same way. Each run re-lists the PMIDs your query currently matches, and anything that has disappeared gets a tombstone row (&lt;code&gt;{"id": "pubmed:&amp;lt;PMID&amp;gt;", "_deleted": true}&lt;/code&gt;). dlt hard-deletes those rows, and cognee's orphan cleanup removes them from the graph and vector store.&lt;/p&gt;

&lt;p&gt;There's one safety rule. If PubMed suddenly returns nothing at all while articles are known, the connector assumes an outage rather than "everything was deleted", and skips the cleanup. It also only saves its progress after a run fully succeeds, so a crash halfway through just means the next run repeats the work.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Abstracts only.&lt;/strong&gt; Full text lives in PubMed Central, which is a separate API.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Corrections to an existing record aren't picked up&lt;/strong&gt;, because a revision doesn't change the Entrez date. NCBI tracks revisions with a different date field (&lt;code&gt;mdat&lt;/code&gt;), which would be a natural next step.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Very broad queries are expensive.&lt;/strong&gt; Something like &lt;code&gt;cancer&lt;/code&gt; matches millions of records. Narrow the query or set &lt;code&gt;mindate&lt;/code&gt; before your first run.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;The interesting part of a connector isn't fetching data, it's deciding what &lt;em&gt;changed&lt;/em&gt; and what should be &lt;em&gt;forgotten&lt;/em&gt;. Each of the three issues above came from asking "what happens on the second run?" instead of only testing the first.&lt;/p&gt;

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

&lt;p&gt;The connector is in review as &lt;a href="https://github.com/topoteretes/cognee-community/pull/275" rel="noopener noreferrer"&gt;topoteretes/cognee-community#275&lt;/a&gt;, for &lt;a href="https://github.com/topoteretes/cognee/issues/4807" rel="noopener noreferrer"&gt;issue #4807&lt;/a&gt;. Until it's merged, you can install it from my branch:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="s2"&gt;"cognee-community-connector-pubmed @ git+https://github.com/priyansh-narang2308/cognee-community@feature/pubmed-connector#subdirectory=packages/connector/pubmed"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're working on research assistants or literature-review tools, I'd love to hear which queries you'd point it at.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>cognee</category>
      <category>development</category>
      <category>memory</category>
    </item>
    <item>
      <title>RepairVision: An Intelligent Repair Café Platform for Multimodal Fault Diagnosis and Sustainable Electronics Repair</title>
      <dc:creator>Priyansh Narang</dc:creator>
      <pubDate>Thu, 08 Oct 2026 10:12:32 +0000</pubDate>
      <link>https://dev.to/priyansh_narang_c1b96ab75/repairvision-an-intelligent-repair-cafe-platform-for-multimodal-fault-diagnosis-and-sustainable-4jm2</link>
      <guid>https://dev.to/priyansh_narang_c1b96ab75/repairvision-an-intelligent-repair-cafe-platform-for-multimodal-fault-diagnosis-and-sustainable-4jm2</guid>
      <description>&lt;p&gt;&lt;strong&gt;Every week, perfectly fixable things end up in the bin.&lt;/strong&gt; A kettle with a tripped fuse. A lamp with a loose wire. A jumper with a hole in the elbow. Repair cafés exist to stop that: free, volunteer-run sessions where people bring something broken and fix it together.&lt;/p&gt;

&lt;p&gt;For &lt;strong&gt;Hacktoberfest Hack Day Coimbatore 2026&lt;/strong&gt;, our team, &lt;strong&gt;The Blacklisted&lt;/strong&gt;, built &lt;strong&gt;RepairVision&lt;/strong&gt;: a platform that runs a repair café day to day and adds AI where it genuinely helps: working out what is wrong, and deciding whether a repair is worth it.&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%2Fenkjmzwj9kn3k0yylfjf.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%2Fenkjmzwj9kn3k0yylfjf.png" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Repair cafés do great work, but three things hold them back:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Running a session is chaotic.&lt;/strong&gt; Paper sign-in sheets, a queue of visitors, keeping track of who is fixing what.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Diagnosis depends on who turns up.&lt;/strong&gt; The volunteer who knows bikes might not know hairdryers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;People don't know if repair is worth it.&lt;/strong&gt; Without a clear comparison, the default is "just buy a new one".&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What we built
&lt;/h2&gt;

&lt;p&gt;RepairVision follows an item from "it's broken" to "it's fixed":&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%2Fqrl1a8wfgtoxrvzy0und.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%2Fqrl1a8wfgtoxrvzy0und.png" alt=" " width="799" height="313"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  AI Diagnosis
&lt;/h3&gt;

&lt;p&gt;Describe the device and add a photo. &lt;strong&gt;Gemma 4&lt;/strong&gt; suggests up to three likely faults, each with the evidence that supports it &lt;em&gt;and the evidence that's still missing&lt;/em&gt;, then recommends safe checks and asks one question at a time to narrow it down.&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%2Fss4uaammuftv7yy9q3k0.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%2Fss4uaammuftv7yy9q3k0.png" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every result is labelled as provisional, and a hazard screen that doesn't depend on the model keeps risky devices (mains appliances, e-bike batteries) on the safe side. Each suggestion is backed by our own repair knowledge base of 100+ entries, the café's past repairs and iFixit guides.&lt;/p&gt;

&lt;h3&gt;
  
  
  Repair or Replace?
&lt;/h3&gt;

&lt;p&gt;The question people actually ask next: &lt;em&gt;is it worth fixing?&lt;/em&gt; The advisor compares the repair cost with a replacement, allows for the chance the repair fails, and shows the CO₂ and waste a repair avoids, with all the working shown.&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%2Fet7c53e72jkhrsk9hbub.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%2Fet7c53e72jkhrsk9hbub.png" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  A site assistant that knows what's happening
&lt;/h3&gt;

&lt;p&gt;A friendly 3D character sits in the corner of every page. Ask it &lt;em&gt;"What events are on right now?"&lt;/em&gt; and it answers from the café's &lt;strong&gt;live data&lt;/strong&gt;, then opens the right page for you.&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%2Fqvol10fcvqoit6ks6egp.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%2Fqvol10fcvqoit6ks6egp.png" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It only ever sees what the public website already shows, so it can't leak anyone's name, contact details or repair, whatever you ask it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Everything a café needs on the day
&lt;/h3&gt;

&lt;p&gt;QR check-in, a live repair queue, a waiting-room display, session planning and impact statistics. All of it is in one place, in a clean dark interface.&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%2F3aruqwbg4x7hgmhwcuvf.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%2F3aruqwbg4x7hgmhwcuvf.png" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Flw61uh0ktuf8rb6ipi96.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%2Flw61uh0ktuf8rb6ipi96.png" alt=" " width="800" height="500"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How we built it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frontend:&lt;/strong&gt; SvelteKit, TypeScript and Tailwind CSS&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backend:&lt;/strong&gt; a single Cloudflare Worker serving the pages and the API&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Data:&lt;/strong&gt; Cloudflare D1 (SQLite) with Drizzle ORM, and R2 for photos&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI:&lt;/strong&gt; Google &lt;strong&gt;Gemma 4&lt;/strong&gt; (&lt;code&gt;gemma-4-26b-a4b-it&lt;/code&gt;) through the Gemini API&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A few decisions we're proud of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;We never trust raw AI output.&lt;/strong&gt; Gemma's replies must be valid JSON that passes a schema, and safety rules run in our own code afterwards.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No invented answers.&lt;/strong&gt; If the AI is unavailable, people see an honest error. Prices are never made up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Privacy by design.&lt;/strong&gt; The public assistant reads only public data, and photos sent for diagnosis aren't stored.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Free to run.&lt;/strong&gt; Cloudflare's free tier plus Gemma via the Gemini API means a volunteer café can host it at no cost.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tested offline.&lt;/strong&gt; All 199 tests use fake AI responses and run in the real Workers runtime.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Challenges and what we learned
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Making AI safe to show to the public&lt;/strong&gt; took more than a good prompt. Strict schemas and code-level guardrails made the difference.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Four people, one codebase, one day.&lt;/strong&gt; Small focused commits, shared types in one package and a fast test suite let us merge each other's work without breaking things.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Free-tier limits shape design.&lt;/strong&gt; Working within about 10 ms of CPU per request pushed us towards caching and keeping AI calls as network waits rather than computation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Deploying it for a real repair café and gathering feedback from volunteers&lt;/li&gt;
&lt;li&gt;Live part prices in the Repair or Replace advisor&lt;/li&gt;
&lt;li&gt;Growing the knowledge base with every repair the community completes&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Code:&lt;/strong&gt; &lt;a href="https://github.com/Kanishk-Rungta/RepairVision_AI" rel="noopener noreferrer"&gt;https://github.com/Kanishk-Rungta/RepairVision_AI&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Live demo:&lt;/strong&gt; &lt;a href="https://repair-cafe-hub.circularity-cloudflare.workers.dev" rel="noopener noreferrer"&gt;https://repair-cafe-hub.circularity-cloudflare.workers.dev&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Demo video:&lt;/strong&gt; &lt;a href="https://drive.google.com/drive/folders/18YFLWch7_9b4JTImlz_1wdl1-F-F6WVc?usp=drive_link" rel="noopener noreferrer"&gt;https://drive.google.com/drive/folders/18YFLWch7_9b4JTImlz_1wdl1-F-F6WVc?usp=drive_link&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Built by &lt;strong&gt;The Blacklisted&lt;/strong&gt;: &lt;br&gt;
Kanishk Rungta, Keertan Vasani, Adiseshan Ramanan and Priyansh Narang.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Fix more. Throw away less.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>hackathon</category>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
