<?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: Shikhar Goel</title>
    <description>The latest articles on DEV Community by Shikhar Goel (@shikhar_goel_68bfaa373346).</description>
    <link>https://dev.to/shikhar_goel_68bfaa373346</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%2F4168975%2F0b76d52e-52d7-4c8f-8fee-dce6a28a33c7.png</url>
      <title>DEV Community: Shikhar Goel</title>
      <link>https://dev.to/shikhar_goel_68bfaa373346</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shikhar_goel_68bfaa373346"/>
    <language>en</language>
    <item>
      <title>Turning My Twitch Follow Graph Into Agent Memory (Twitch x Cognee)</title>
      <dc:creator>Shikhar Goel</dc:creator>
      <pubDate>Sat, 31 Oct 2026 18:26:51 +0000</pubDate>
      <link>https://dev.to/shikhar_goel_68bfaa373346/turning-my-twitch-follow-graph-into-agent-memory-twitch-x-cognee-2jpp</link>
      <guid>https://dev.to/shikhar_goel_68bfaa373346/turning-my-twitch-follow-graph-into-agent-memory-twitch-x-cognee-2jpp</guid>
      <description>&lt;p&gt;&lt;em&gt;This is my Mergetober hackathon entry for the &lt;a href="https://github.com/topoteretes/cognee-community/pull/600" rel="noopener noreferrer"&gt;Twitch connector&lt;/a&gt; (proposed in &lt;a href="https://github.com/topoteretes/cognee/issues/6489" rel="noopener noreferrer"&gt;cognee#6489&lt;/a&gt;).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I follow a few dozen Twitch channels, and a surprising amount of what I "know" lives in their VODs — conference talks, deep-dive coding sessions, speedrun history. Twitch's own search is famously bad at "which of my channels streamed about Rust last month, and what was that VOD where they rebuilt their parser?"&lt;/p&gt;

&lt;p&gt;So I connected the follow graph to &lt;strong&gt;cognee&lt;/strong&gt;: the channels I follow, each channel's description and current category, and their VODs and clips become searchable memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting
&lt;/h2&gt;

&lt;p&gt;Register a free app at dev.twitch.tv/console, mint a user token with &lt;code&gt;user:read:follows&lt;/code&gt; (the README walks the one-click flow), and:&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;cognee&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;cognee_community_connector_twitch&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;twitch_source&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;twitch_source&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;access_token&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;twitch&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;answers&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;recall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Which of the channels I follow recently streamed about Rust, and what were those VODs about?&lt;/span&gt;&lt;span class="sh"&gt;"&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;twitch&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Design notes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Full re-list + diff.&lt;/strong&gt; Each sync re-lists follows and each channel's VODs/clips (paginated), then diffs against the previous run: unfollowing a channel forgets the channel &lt;em&gt;and&lt;/em&gt; all its stored VOD/clip documents; a VOD that expired is forgotten too. The diff drives &lt;code&gt;_deleted&lt;/code&gt; hard-delete rows that orphan cleanup physically removes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stable rows under churn.&lt;/strong&gt; &lt;code&gt;view_count&lt;/code&gt; is deliberately excluded from rendered content — view counts change constantly, and including them would re-process every VOD on every sync. A VOD's row is byte-identical until its title or description changes. Durations parse from Helix's &lt;code&gt;PT2H14M33S&lt;/code&gt; into something a human and an LLM both read (&lt;code&gt;2h 14m 33s&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Graph linking.&lt;/strong&gt; Every VOD and clip row carries a &lt;code&gt;channel:&amp;lt;login&amp;gt;&lt;/code&gt; node set, so a channel's content stays connected in the graph.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verified against the docs, not memory:&lt;/strong&gt; &lt;code&gt;/channels&lt;/code&gt; takes repeated &lt;code&gt;broadcaster_id&lt;/code&gt; params and does &lt;em&gt;not&lt;/em&gt; return descriptions — channel descriptions live on the user object, so they're fetched from batched &lt;code&gt;GET /users&lt;/code&gt;. Details like this are why you check the reference.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limits.&lt;/strong&gt; &lt;code&gt;Ratelimit-Remaining&lt;/code&gt; is honoured; a wait over 60s stops the run cleanly with nothing lost. &lt;code&gt;max_channels&lt;/code&gt; (default 25) bounds big follow lists, and a &lt;code&gt;channels=&lt;/code&gt; filter syncs a subset.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Verified so far
&lt;/h2&gt;

&lt;p&gt;The connector runs end-to-end today: the full suite passes (every Helix endpoint verified against the official docs), including delivery tests that drive a real dlt pipeline and prove an unfollowed channel — and its VODs and clips — physically disappears after the &lt;code&gt;_deleted&lt;/code&gt; tombstones.&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="nv"&gt;$ &lt;/span&gt;uv run pytest tests/ &lt;span class="nt"&gt;-q&lt;/span&gt;
55 passed &lt;span class="k"&gt;in &lt;/span&gt;3.94s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'll add numbers from my own follow list here as the syncs run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest limitations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;No live-chat history — Helix doesn't expose it.&lt;/li&gt;
&lt;li&gt;The clips endpoint returns a view-count top-list (~1000 clips), so a clip falling out of the top listing is forgotten even if it still exists. Documented, not hidden.&lt;/li&gt;
&lt;li&gt;VODs expire on Twitch's schedule; the connector treats that as a deletion, which is the correct memory semantics.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The connector is at &lt;code&gt;packages/connector/twitch/&lt;/code&gt; in &lt;a href="https://github.com/topoteretes/cognee-community/pull/600" rel="noopener noreferrer"&gt;cognee-community PR #600&lt;/a&gt; — 55 tests, every endpoint verified against dev.twitch.tv, delivery tests proving unfollow/expiry physically removes documents from a real dlt pipeline.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>cognee</category>
      <category>twitch</category>
      <category>python</category>
    </item>
    <item>
      <title>An Agent That Knows What I Watched, When, and Where I Left Off (Trakt x Cognee)</title>
      <dc:creator>Shikhar Goel</dc:creator>
      <pubDate>Sat, 31 Oct 2026 18:24:43 +0000</pubDate>
      <link>https://dev.to/shikhar_goel_68bfaa373346/an-agent-that-knows-what-i-watched-when-and-where-i-left-off-trakt-x-cognee-h8i</link>
      <guid>https://dev.to/shikhar_goel_68bfaa373346/an-agent-that-knows-what-i-watched-when-and-where-i-left-off-trakt-x-cognee-h8i</guid>
      <description>&lt;p&gt;&lt;em&gt;This is my Mergetober hackathon entry for the &lt;a href="https://github.com/topoteretes/cognee-community/pull/599" rel="noopener noreferrer"&gt;Trakt connector&lt;/a&gt; (proposed in &lt;a href="https://github.com/topoteretes/cognee/issues/6488" rel="noopener noreferrer"&gt;cognee#6488&lt;/a&gt;).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Trakt is the quiet layer that has tracked everything I've watched for years — scrobbled automatically from my media setup, every episode with a timestamp. It's the closest thing I have to a ground-truth viewing record, and until now it was write-only memory: tracked, stored, never used.&lt;/p&gt;

&lt;p&gt;This connector makes it agent-readable. With cognee, "which show was I bingeing in March?", "what episode did I last watch of &lt;em&gt;Severance&lt;/em&gt;?" and "what's rated highly on my watchlist but never watched?" become answerable from my actual history.&lt;/p&gt;

&lt;h2&gt;
  
  
  Connecting
&lt;/h2&gt;

&lt;p&gt;Trakt's official OAuth PIN flow, walked through in the README: create a free app at trakt.tv/oauth/applications, run the built-in helper, paste the PIN once:&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;from&lt;/span&gt; &lt;span class="n"&gt;cognee_community_connector_trakt&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;trakt_pin_flow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;trakt_source&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;cognee&lt;/span&gt;

&lt;span class="nf"&gt;trakt_pin_flow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client_secret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# prints a URL, asks for the PIN, saves the token
&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;trakt_source&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;access_token&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;trakt&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;answers&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;recall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;What shows was I watching recently, and where did I leave off?&lt;/span&gt;&lt;span class="sh"&gt;"&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;trakt&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What gets remembered — and how it stays cheap
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Movies, episodes and shows I've watched (dates, play counts, titles), my ratings, my watchlist and my lists — one document each, with &lt;code&gt;show:&amp;lt;slug&amp;gt;&lt;/code&gt; and &lt;code&gt;list:&amp;lt;slug&amp;gt;&lt;/code&gt; node sets so episodes link to their show and lists link to their members in the graph, no LLM required.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Incremental by section, not by everything.&lt;/strong&gt; Trakt exposes &lt;code&gt;/sync/last_activities&lt;/code&gt; — one timestamp per section of your history. The connector stores those and re-pulls only what moved. An idle account costs one request per sync. Within a changed section it re-lists fully and upserts via &lt;code&gt;merge&lt;/code&gt;, so unchanged rows keep their content hash and are never re-processed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ratings don't get lost.&lt;/strong&gt; A movie you rated but never watched still becomes a document. Unwatching a rated movie &lt;em&gt;converts&lt;/em&gt; its document rather than deleting it — the cross-section forget-on-delete only tombstones a key when no section claims it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Token hygiene and limits.&lt;/strong&gt; The client refreshes expired tokens itself, honours &lt;code&gt;Retry-After&lt;/code&gt;, and a rate-limit wait longer than 60 seconds stops the run cleanly with nothing confirmed — the next sync resumes. The token never appears in a repr, log or error.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Verified so far
&lt;/h2&gt;

&lt;p&gt;The connector runs end-to-end today: the full suite passes (every endpoint verified against the official Trakt API contract), including a delivery test that drives a real dlt pipeline and proves an item removed from history physically disappears after the &lt;code&gt;_deleted&lt;/code&gt; tombstone.&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="nv"&gt;$ &lt;/span&gt;uv run pytest tests/ &lt;span class="nt"&gt;-q&lt;/span&gt;
42 passed &lt;span class="k"&gt;in &lt;/span&gt;3.87s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'll add numbers from my own history here as the syncs run — including the one-request idle sync.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest limitations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Watched-show payloads don't carry episode ids, so the connector resolves them per-show from the seasons endpoint once (documented fallback if a show is delisted).&lt;/li&gt;
&lt;li&gt;Rate limits are real (500 GETs / 5 min per app); big first syncs page through and stop cleanly rather than hammering.&lt;/li&gt;
&lt;li&gt;History privacy: this is your full viewing record going into your own cognee dataset — keep the dataset private.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The connector is at &lt;code&gt;packages/connector/trakt/&lt;/code&gt; in &lt;a href="https://github.com/topoteretes/cognee-community/pull/599" rel="noopener noreferrer"&gt;cognee-community PR #599&lt;/a&gt;, with 42 tests (every endpoint verified against the official Trakt API contract) and delivery tests proving physical deletion on unwatched items.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>cognee</category>
      <category>trakt</category>
      <category>python</category>
    </item>
    <item>
      <title>My AI Now Remembers Every Film I've Watched — and What I Thought of It (Letterboxd x Cognee)</title>
      <dc:creator>Shikhar Goel</dc:creator>
      <pubDate>Sat, 31 Oct 2026 18:24:30 +0000</pubDate>
      <link>https://dev.to/shikhar_goel_68bfaa373346/my-ai-now-remembers-every-film-ive-watched-and-what-i-thought-of-it-letterboxd-x-cognee-4kk4</link>
      <guid>https://dev.to/shikhar_goel_68bfaa373346/my-ai-now-remembers-every-film-ive-watched-and-what-i-thought-of-it-letterboxd-x-cognee-4kk4</guid>
      <description>&lt;p&gt;&lt;em&gt;This is my Mergetober hackathon entry for the &lt;a href="https://github.com/topoteretes/cognee-community/pull/598" rel="noopener noreferrer"&gt;Letterboxd connector&lt;/a&gt; (proposed in &lt;a href="https://github.com/topoteretes/cognee/issues/6487" rel="noopener noreferrer"&gt;cognee#6487&lt;/a&gt;).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Letterboxd is where I've logged every film I've watched for years — with ratings, diary entries, rewatch notes and the occasional review I'm proud of. It's a rich, personal dataset sitting behind a social UI that can't do anything except show it back to me chronologically.&lt;/p&gt;

&lt;p&gt;This connector pipes the official Letterboxd export into &lt;strong&gt;cognee&lt;/strong&gt;, so an agent can actually answer questions over my film history: "what did I think of Denis Villeneuve's filmography?", "which noirs did I log while studying noir?", "how many times have I rewatched Parasite?"&lt;/p&gt;

&lt;h2&gt;
  
  
  One ZIP, zero credentials
&lt;/h2&gt;

&lt;p&gt;Letterboxd has no public API, but every member can export everything (Settings → Import &amp;amp; Export → Export). The connector takes the ZIP — or an extracted folder — and that's the entire setup:&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;cognee&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;cognee_community_connector_letterboxd&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;letterboxd_source&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;letterboxd_source&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;letterboxd_export.zip&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;letterboxd&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;answers&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;recall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Which films did I rate highest, and what did I think of them?&lt;/span&gt;&lt;span class="sh"&gt;"&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;letterboxd&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What gets remembered
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One document per film&lt;/strong&gt;, merged across &lt;code&gt;watched.csv&lt;/code&gt;, &lt;code&gt;ratings.csv&lt;/code&gt;, &lt;code&gt;reviews.csv&lt;/code&gt; and &lt;code&gt;diary.csv&lt;/code&gt; — my rating rendered as stars (the 10-point scale becomes half-star precision, so 7.5 → ★★★½), my full review text, and a date-ordered watch timeline with rewatch markers and tags.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One document per list&lt;/strong&gt;, with the ordered roster — so "my favourite slow cinema" list is itself a memorable object.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Graph links for free:&lt;/strong&gt; every film row carries node-set keys for the lists it belongs to and its decade, so films connect across lists and eras without any LLM extraction.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Incremental + forget-on-delete
&lt;/h2&gt;

&lt;p&gt;Rows are keyed by the stable public film URI and rendered deterministically, so re-exporting and re-running upserts only what changed (an edited review touches exactly one row). And it works in both directions: films or lists present last time but gone from the new export are emitted as hard-delete markers, and cognee's orphan cleanup physically removes them from the graph and vector stores. The memory tracks the library, not a stale snapshot of it.&lt;/p&gt;

&lt;p&gt;The parser matches columns by name and ignores unknown ones, so Letterboxd adding columns to future exports won't break it. The only fatal error is a missing &lt;code&gt;watched.csv&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verified so far
&lt;/h2&gt;

&lt;p&gt;The connector runs end-to-end today: the full suite passes against fixtures mirroring the real CSV shapes (rewatches, tags, list rosters), and the delivery tests drive a real dlt pipeline where an edited review updates its row in place and a deleted film or list physically disappears after the &lt;code&gt;_deleted&lt;/code&gt; tombstone.&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="nv"&gt;$ &lt;/span&gt;uv run pytest tests/ &lt;span class="nt"&gt;-q&lt;/span&gt;
39 passed &lt;span class="k"&gt;in &lt;/span&gt;5.11s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'll add numbers from my own export here as the syncs run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest limitations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Exports are snapshots: edits/removals are detected on the next export + sync.&lt;/li&gt;
&lt;li&gt;Older exports don't include the watchlist.&lt;/li&gt;
&lt;li&gt;No half-star &lt;em&gt;diary&lt;/em&gt; ratings before 10-point conversion existed — handled by the same conversion.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The connector lives at &lt;code&gt;packages/connector/letterboxd/&lt;/code&gt; in &lt;a href="https://github.com/topoteretes/cognee-community/pull/598" rel="noopener noreferrer"&gt;cognee-community PR #598&lt;/a&gt; — 39 tests against the real CSV shapes (rewatches, tags, list rosters, tolerant parsing) plus delivery tests proving edits upsert and deletions physically disappear.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>cognee</category>
      <category>movies</category>
      <category>python</category>
    </item>
    <item>
      <title>I Gave My AI a Memory of Every Book I've Read (Goodreads x Cognee)</title>
      <dc:creator>Shikhar Goel</dc:creator>
      <pubDate>Sat, 31 Oct 2026 18:24:09 +0000</pubDate>
      <link>https://dev.to/shikhar_goel_68bfaa373346/i-gave-my-ai-a-memory-of-every-book-ive-read-goodreads-x-cognee-4pf8</link>
      <guid>https://dev.to/shikhar_goel_68bfaa373346/i-gave-my-ai-a-memory-of-every-book-ive-read-goodreads-x-cognee-4pf8</guid>
      <description>&lt;p&gt;&lt;em&gt;This is my Mergetober hackathon entry for the &lt;a href="https://github.com/topoteretes/cognee/pull/597" rel="noopener noreferrer"&gt;Goodreads connector&lt;/a&gt; (PR, proposed in &lt;a href="https://github.com/topoteretes/cognee/issues/6486" rel="noopener noreferrer"&gt;cognee#6486&lt;/a&gt;).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I've tracked my reading on Goodreads since 2016. Nine years of data: every book, every rating, every half-finished review I typed into a phone at 1am. It's the most complete record of what I've &lt;em&gt;learned and felt&lt;/em&gt; that I own — and it's completely inert. Goodreads won't answer a question for me. It certainly won't tell my AI assistant what I thought of &lt;em&gt;Thinking, Fast and Slow&lt;/em&gt; or which books on systems design I've already absorbed.&lt;/p&gt;

&lt;p&gt;So I built the missing pipe: a &lt;strong&gt;cognee connector that turns a Goodreads library export into agent memory&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why an export parser and not an API
&lt;/h2&gt;

&lt;p&gt;Goodreads retired its public API in 2020. The library export (Settings → Import/Export → "Export Library") is the only supported access path left — and it's actually a &lt;em&gt;better&lt;/em&gt; one for this use case: one file, everything in it, no rate limits, no OAuth. The connector takes the CSV path, no credentials:&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;cognee&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;cognee_community_connector_goodreads&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;goodreads_source&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;goodreads_source&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;goodreads_library_export.csv&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;goodreads&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="c1"&gt;# required: see below
&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;answers&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;recall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Which books did I read about distributed systems, and what did I think of them?&lt;/span&gt;&lt;span class="sh"&gt;"&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;goodreads&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What actually ends up in memory
&lt;/h2&gt;

&lt;p&gt;Every book becomes one document: title, author, year, ISBN, shelves, status (read / currently reading / to-read), average rating — and the interesting part, &lt;strong&gt;my per-read timeline&lt;/strong&gt;. Goodreads exports re-reads as repeated &lt;code&gt;Date Read&lt;/code&gt; / &lt;code&gt;My Rating&lt;/code&gt; / &lt;code&gt;My Review&lt;/code&gt; columns; the connector zips them into a sorted timeline, so a book I read twice keeps both dates, both ratings and both reviews, HTML stripped. Books also link in the graph by shelf and author via node sets — no LLM extraction needed to know that two books share a shelf.&lt;/p&gt;

&lt;h2&gt;
  
  
  The two design decisions that matter
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Incremental sync via &lt;code&gt;merge&lt;/code&gt;.&lt;/strong&gt; Re-export and re-run: only books whose content changed get re-processed, because rows are rendered deterministically (no ingestion timestamps) and keyed by Goodreads' global book id. An unchanged library costs nothing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Forget-on-delete.&lt;/strong&gt; Remove a book from your Goodreads library, re-export, re-sync — the connector diffs the new export against the previous one and emits a hard-delete marker, and cognee's orphan cleanup removes it from the graph &lt;em&gt;and&lt;/em&gt; the vector stores. Your agent's memory of your library tracks your actual library, in both directions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verified so far
&lt;/h2&gt;

&lt;p&gt;The connector runs end-to-end today: the full suite passes against fixtures that mirror the real export format, and the delivery tests drive a real dlt pipeline where an edited review updates its row in place and a removed book physically disappears from the destination after the &lt;code&gt;_deleted&lt;/code&gt; tombstone.&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="nv"&gt;$ &lt;/span&gt;uv run pytest tests/ &lt;span class="nt"&gt;-q&lt;/span&gt;
34 passed &lt;span class="k"&gt;in &lt;/span&gt;2.86s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'll add numbers from my own library export here as the syncs run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Honest limitations
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The export is a snapshot: removals and edits are only detected when you re-export.&lt;/li&gt;
&lt;li&gt;Switching to a different account's export merges by book id (Goodreads ids are global).&lt;/li&gt;
&lt;li&gt;No reading-position data for "currently reading" books — Goodreads doesn't export it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The connector is at &lt;code&gt;packages/connector/goodreads/&lt;/code&gt; in &lt;a href="https://github.com/topoteretes/cognee-community/pull/597" rel="noopener noreferrer"&gt;cognee-community PR #597&lt;/a&gt;, with 34 tests against the real export format (re-read columns, &lt;code&gt;="ISBN"&lt;/code&gt; escaping, footer junk, UTF-8 BOM) and delivery tests that prove edits upsert and deletions physically disappear. Try it: &lt;code&gt;uv add cognee-community-connector-goodreads&lt;/code&gt; once it merges.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>cognee</category>
      <category>goodreads</category>
      <category>python</category>
    </item>
  </channel>
</rss>
