<?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: Shermaine Yap</title>
    <description>The latest articles on DEV Community by Shermaine Yap (@shermaineyap).</description>
    <link>https://dev.to/shermaineyap</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%2F4218640%2F9f656bb4-daa9-4bb4-b0e8-106205b021ee.png</url>
      <title>DEV Community: Shermaine Yap</title>
      <link>https://dev.to/shermaineyap</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shermaineyap"/>
    <language>en</language>
    <item>
      <title>One calendar, two names: fixing duplicate memories in Cognee's Google Calendar connector</title>
      <dc:creator>Shermaine Yap</dc:creator>
      <pubDate>Sat, 31 Oct 2026 08:34:34 +0000</pubDate>
      <link>https://dev.to/shermaineyap/one-calendar-two-names-fixing-duplicate-memories-in-cognees-google-calendar-connector-3593</link>
      <guid>https://dev.to/shermaineyap/one-calendar-two-names-fixing-duplicate-memories-in-cognees-google-calendar-connector-3593</guid>
      <description>&lt;p&gt;AI agents are only as good as what they remember. &lt;a href="https://github.com/topoteretes/cognee" rel="noopener noreferrer"&gt;Cognee&lt;/a&gt; is an open-source memory layer for agents: you hand it documents, chats or data, and it builds a knowledge graph the agent can query later. For Mergetober (WeMakeDevs × Cognee) I spent my contribution on the Google Calendar connector, which turns your calendar into agent memory, so you can ask things like "what did I agree to in last week's design review?" or "when is my next 1:1 with Priya?".&lt;/p&gt;

&lt;h2&gt;
  
  
  How the connector works
&lt;/h2&gt;

&lt;p&gt;The connector is a &lt;code&gt;dlt&lt;/code&gt; source. You hand it to Cognee in one call:&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_google_calendar&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;google_calendar_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;google_calendar_source&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;calendar_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;primary&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;calendar&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="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;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;When is my next design review?&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each event becomes a short text document ("Calendar event: Design review / When: ... / Attendees: ..."), which Cognee chunks and turns into entities and relations. Later syncs use Google's sync tokens, so only changed events are re-read, and deleted events are forgotten.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug: one calendar, two names
&lt;/h2&gt;

&lt;p&gt;In Google Calendar your main calendar has two names: the alias &lt;code&gt;"primary"&lt;/code&gt;, and its real id, which is your email address. The connector's helper script &lt;code&gt;examples/login.py&lt;/code&gt; lists your calendars by their real ids, so a natural setup looks like this:&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;GOOGLE_CALENDAR_IDS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;primary,me@gmail.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The connector treated those as two different calendars. It kept separate sync state for each and built row ids from the configured string (&lt;code&gt;gcal:primary:&amp;lt;event&amp;gt;&lt;/code&gt; and &lt;code&gt;gcal:me@gmail.com:&amp;lt;event&amp;gt;&lt;/code&gt;), so &lt;strong&gt;every event was stored twice&lt;/strong&gt;. That means double the LLM calls during cognify and duplicate facts in the graph. It's the kind of quiet bug that never crashes anything; your agent just gets noisier and more expensive.&lt;/p&gt;

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

&lt;p&gt;The sync already lists your calendars to get their display names, and that listing marks which one is primary. So the fix reuses it:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;List calendars once and find the primary calendar's real id.&lt;/li&gt;
&lt;li&gt;Treat &lt;code&gt;"primary"&lt;/code&gt; and that id as the same key, and keep only the first spelling you configured.&lt;/li&gt;
&lt;li&gt;Leave everything else alone.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Keeping the &lt;em&gt;first&lt;/em&gt; spelling matters: someone with the default &lt;code&gt;["primary"]&lt;/code&gt; setup sees no change at all (same row ids, same sync state, nothing re-ingested). And if an earlier sync already stored both copies, the second spelling now counts as a calendar removed from the config, so the connector's existing forget path cleans the duplicates up on the next run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing it
&lt;/h2&gt;

&lt;p&gt;The connector's tests run against an in-memory fake of the Calendar API, so no Google account is needed. I added three tests: both spellings sync as one calendar, the first spelling is kept, and a leftover duplicate from an older sync gets forgotten. Then I ran the whole suite plus &lt;code&gt;ruff&lt;/code&gt; on Python 3.11 and 3.13 with GitHub Actions on my fork.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Aliases are identity bugs waiting to happen.&lt;/strong&gt; Whenever an API gives one thing two names, anything keyed on the raw string can split it in two.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Read how the data is keyed, not just how it's fetched.&lt;/strong&gt; The bug wasn't in any API call; it was in which string became the key.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fast feedback beats big ideas.&lt;/strong&gt; I first planned a brand-new connector, then found hundreds of connector proposals already queued. A small, well-tested fix to code people already use is easier to review and more useful.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Links:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pull request: &lt;a href="https://github.com/topoteretes/cognee-community/pull/587" rel="noopener noreferrer"&gt;https://github.com/topoteretes/cognee-community/pull/587&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Issue: &lt;a href="https://github.com/topoteretes/cognee/issues/6481" rel="noopener noreferrer"&gt;https://github.com/topoteretes/cognee/issues/6481&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Also reported along the way: &lt;a href="https://github.com/topoteretes/cognee/issues/6470" rel="noopener noreferrer"&gt;https://github.com/topoteretes/cognee/issues/6470&lt;/a&gt; (all-day events stored one day too long)&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>opensource</category>
      <category>python</category>
      <category>ai</category>
      <category>hacktoberfest</category>
    </item>
  </channel>
</rss>
