<?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: Akshat Gupta</title>
    <description>The latest articles on DEV Community by Akshat Gupta (@akshatji).</description>
    <link>https://dev.to/akshatji</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%2F4190533%2Fcf8c8c09-694f-4564-b347-e188429b499d.png</url>
      <title>DEV Community: Akshat Gupta</title>
      <link>https://dev.to/akshatji</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/akshatji"/>
    <language>en</language>
    <item>
      <title>Teaching an AI agent to forget a deleted Redshift row</title>
      <dc:creator>Akshat Gupta</dc:creator>
      <pubDate>Sat, 17 Oct 2026 13:19:20 +0000</pubDate>
      <link>https://dev.to/akshatji/teaching-an-ai-agent-to-forget-a-deleted-redshift-row-2mdf</link>
      <guid>https://dev.to/akshatji/teaching-an-ai-agent-to-forget-a-deleted-redshift-row-2mdf</guid>
      <description>&lt;p&gt;Here is a small horror story.&lt;/p&gt;

&lt;p&gt;An AI agent reads your warehouse. Someone deletes a customer. A week later the agent is still talking about that customer. It sounds completely sure. It is completely wrong.&lt;/p&gt;

&lt;p&gt;Nothing crashed. The agent just kept a memory of a row that no longer exists.&lt;/p&gt;

&lt;p&gt;I wanted to fix that for Amazon Redshift. So for Mergetober I built a connector for Cognee. The code lives in &lt;a href="https://github.com/topoteretes/cognee-community/pull/403" rel="noopener noreferrer"&gt;PR #403&lt;/a&gt; for &lt;a href="https://github.com/topoteretes/cognee/issues/4773" rel="noopener noreferrer"&gt;issue #4773&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I had never used Redshift before this. I wanted to see how a warehouse really works under the hood. Building a connector seemed like a good excuse.&lt;/p&gt;

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

&lt;p&gt;&lt;a href="https://github.com/topoteretes/cognee" rel="noopener noreferrer"&gt;Cognee&lt;/a&gt; turns documents into a knowledge graph that an agent can search. The connector feeds it from Redshift. You pick the tables or SQL queries. Then:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Every row becomes a short document.&lt;/li&gt;
&lt;li&gt;Every table gets a summary page built from its comments.&lt;/li&gt;
&lt;li&gt;Later runs read only what changed.&lt;/li&gt;
&lt;li&gt;Deleted rows are forgotten.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The summary page is my favourite part. Say a column is called &lt;code&gt;priority&lt;/code&gt;. On its own that tells the model nothing. Add the comment "p1 = outage, p4 = question" and it knows what a p1 ticket is.&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;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;redshift_source&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nc"&gt;RedshiftTable&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;public.support_tickets&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;ticket_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;cursor_column&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;updated_at&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)],&lt;/span&gt;
    &lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nc"&gt;DataApiConnection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;database&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dev&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;workgroup_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;cognee-test&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="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="n"&gt;source&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;redshift&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Getting in without a tunnel
&lt;/h2&gt;

&lt;p&gt;Redshift usually hides in a private network. The classic trick is an SSH tunnel through a bastion host. It works. It is also a chore.&lt;/p&gt;

&lt;p&gt;So I used the Data API instead. It is a plain HTTPS endpoint. You send a statement. You wait until it is done. You read the result pages. AWS runs it inside your network for you.&lt;/p&gt;

&lt;p&gt;No tunnel. No driver. No open port. It works with IAM or a Secrets Manager secret. A direct driver connection is still there as a backup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Catching deletes
&lt;/h2&gt;

&lt;p&gt;Redshift has no change feed. Nobody tells you a row is gone.&lt;/p&gt;

&lt;p&gt;So every run also asks for the primary keys. That is one column and a columnar store reads it fast. If a key was there last time and is missing now then the row is gone. The connector removes it from the graph.&lt;/p&gt;

&lt;p&gt;Two safety rules keep this honest:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A failed query never deletes anything. The run stops and the next one starts from the same spot.&lt;/li&gt;
&lt;li&gt;A row that arrives late with an old timestamp is still found. The key list catches it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The real test
&lt;/h2&gt;

&lt;p&gt;First I tested against fakes. A SQLite stand-in. A stubbed AWS API. A real Postgres. 59 tests passed. But a fake only proves what you thought to fake.&lt;/p&gt;

&lt;p&gt;So I created a real Redshift Serverless workgroup in eu-north-1 and filled a small ticket table.&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%2F291ab8cvi2cfe23m7my0.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%2F291ab8cvi2cfe23m7my0.png" alt="The real run" width="799" height="396"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first sync took in 6 rows. I asked what the table holds. The answer even explained what p1 means. That came straight from the column comment in Redshift.&lt;/p&gt;

&lt;p&gt;Then I changed ticket 3 and deleted ticket 2 right inside Redshift. The second sync reported &lt;code&gt;rows: 3, deleted: 3&lt;/code&gt;. The deleted ticket's customer vanished from both the documents and the graph. The other two stayed.&lt;/p&gt;

&lt;p&gt;That is the behaviour I wanted.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bug only a real cluster could find
&lt;/h2&gt;

&lt;p&gt;My first run failed with &lt;code&gt;Redshift endpoint doesn't exist in this region&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;My docs told people to set &lt;code&gt;AWS_REGION&lt;/code&gt;. But boto3 does not read that variable. It reads &lt;code&gt;AWS_DEFAULT_REGION&lt;/code&gt; or your profile. My profile said us-east-1 and my cluster lived in eu-north-1. No fake would ever have shown me this.&lt;/p&gt;

&lt;p&gt;The connector now honours &lt;code&gt;AWS_REGION&lt;/code&gt; and a test guards it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I did not test
&lt;/h2&gt;

&lt;p&gt;The direct connection through an SSH or SSM tunnel only has unit tests. The Data API is the default so that is the path I ran live. I would rather say that than hide it.&lt;/p&gt;

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

&lt;p&gt;The example and a seed file live in &lt;code&gt;packages/connector/redshift/&lt;/code&gt;. Redshift Serverless has a free trial for new users so a short test costs very little. Delete the workgroup when you finish. It keeps billing until you do.&lt;/p&gt;

&lt;p&gt;If you point it at your own warehouse I would love to hear what breaks.&lt;/p&gt;

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