<?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: Akhil</title>
    <description>The latest articles on DEV Community by Akhil (@d_akhil_kumar).</description>
    <link>https://dev.to/d_akhil_kumar</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%2F3480151%2F77fde976-0f63-40ea-81bf-134f40cc4b42.png</url>
      <title>DEV Community: Akhil</title>
      <link>https://dev.to/d_akhil_kumar</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/d_akhil_kumar"/>
    <language>en</language>
    <item>
      <title>Ever wondered what exactly happens inside PostgreSQL when you run an INSERT? Let's break down shared buffers, the WAL, MVCC, and building a CDC pipeline.</title>
      <dc:creator>Akhil</dc:creator>
      <pubDate>Thu, 23 Jul 2026 11:00:49 +0000</pubDate>
      <link>https://dev.to/d_akhil_kumar/ever-wondered-what-exactly-happens-inside-postgresql-when-you-run-an-insert-lets-break-down-5781</link>
      <guid>https://dev.to/d_akhil_kumar/ever-wondered-what-exactly-happens-inside-postgresql-when-you-run-an-insert-lets-break-down-5781</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/d_akhil_kumar/real-time-data-pipelines-postgres-debezium-and-clickhouse-explained-2ech" class="crayons-story__hidden-navigation-link"&gt;Real-Time Data Pipelines: Postgres, Debezium, and ClickHouse Explained&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/d_akhil_kumar" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F3480151%2F77fde976-0f63-40ea-81bf-134f40cc4b42.png" alt="d_akhil_kumar profile" class="crayons-avatar__image" width="460" height="460"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/d_akhil_kumar" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Akhil
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Akhil
                
              
              &lt;div id="story-author-preview-content-4214448" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/d_akhil_kumar" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F3480151%2F77fde976-0f63-40ea-81bf-134f40cc4b42.png" class="crayons-avatar__image" alt="" width="460" height="460"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Akhil&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/d_akhil_kumar/real-time-data-pipelines-postgres-debezium-and-clickhouse-explained-2ech" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jul 23&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/d_akhil_kumar/real-time-data-pipelines-postgres-debezium-and-clickhouse-explained-2ech" id="article-link-4214448"&gt;
          Real-Time Data Pipelines: Postgres, Debezium, and ClickHouse Explained
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/postgres"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;postgres&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/database"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;database&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/kafka"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;kafka&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/dataengineering"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;dataengineering&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
            &lt;a href="https://dev.to/d_akhil_kumar/real-time-data-pipelines-postgres-debezium-and-clickhouse-explained-2ech#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            5 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success crayons-icon c-btn__icon"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>architecture</category>
      <category>backend</category>
      <category>data</category>
      <category>database</category>
    </item>
    <item>
      <title>Real-Time Data Pipelines: Postgres, Debezium, and ClickHouse Explained</title>
      <dc:creator>Akhil</dc:creator>
      <pubDate>Thu, 23 Jul 2026 10:59:30 +0000</pubDate>
      <link>https://dev.to/d_akhil_kumar/real-time-data-pipelines-postgres-debezium-and-clickhouse-explained-2ech</link>
      <guid>https://dev.to/d_akhil_kumar/real-time-data-pipelines-postgres-debezium-and-clickhouse-explained-2ech</guid>
      <description>&lt;p&gt;If you’ve ever built a backend, you’ve probably written an &lt;code&gt;INSERT&lt;/code&gt; statement, gotten a &lt;code&gt;200 OK&lt;/code&gt; back, and moved on with your life. &lt;/p&gt;

&lt;p&gt;But what &lt;em&gt;actually&lt;/em&gt; happens inside the database during that millisecond? How does the database guarantee it won't lose your data if someone unplugs the server? And how do massive companies stream millions of these changes in real-time to analytics dashboards without crashing their main database?&lt;/p&gt;

&lt;p&gt;Let’s dive deep into the internals of PostgreSQL, moving from a single query on a hard drive all the way up to a distributed Change Data Capture (CDC) pipeline.&lt;/p&gt;

&lt;p&gt;To make this concrete, imagine we are building the backend for &lt;strong&gt;QuickBite&lt;/strong&gt;, a massive food delivery app. We have thousands of delivery drivers updating their live GPS locations every few seconds.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. The RAM vs. Disk Reality (How INSERT Works)
&lt;/h2&gt;

&lt;p&gt;When our backend executes:&lt;br&gt;
&lt;code&gt;INSERT INTO driver_locations (driver_id, lat, long) VALUES (105, 40.71, -74.00);&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;You might think Postgres immediately writes this new row to the hard drive (SSD). &lt;strong&gt;It doesn't.&lt;/strong&gt; Writing directly to a massive table file is a "random I/O" operation, which is incredibly slow. &lt;/p&gt;

&lt;p&gt;Instead, Postgres does this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The Free Space Map (FSM):&lt;/strong&gt; Postgres checks a lightweight map to find an 8KB page on the disk that has empty space.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shared Buffers:&lt;/strong&gt; It copies that entire 8KB page from the slow SSD into a dedicated chunk of RAM called &lt;strong&gt;shared_buffers&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Dirty Page:&lt;/strong&gt; It inserts the new GPS coordinates into that page in RAM, and marks the page as &lt;strong&gt;"dirty"&lt;/strong&gt; (meaning the RAM version is now newer than the disk version).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If another user runs a &lt;code&gt;SELECT&lt;/code&gt; query to find that driver, Postgres checks a Hash Map, realizes the page is already in RAM, and serves it instantly without touching the disk.&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%2Frzc3ukq50ddz7nkon9uk.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%2Frzc3ukq50ddz7nkon9uk.png" alt="Postgres Buffer Architecture" width="781" height="441"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Notice how data moves from Shared Buffers to the WAL before ever hitting the main Data Files.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  2. The Lifesaver: The Write-Ahead Log (WAL)
&lt;/h2&gt;

&lt;p&gt;Here is the obvious problem: If the new location is only in RAM, what happens if the server loses power? The data is gone forever, right?&lt;/p&gt;

&lt;p&gt;Enter the &lt;strong&gt;Write-Ahead Log (WAL)&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Before Postgres tells your backend "Insert Successful!", it appends the exact byte-level change to the WAL file on the disk. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why is writing to the WAL fast, but writing to the table slow?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;The Table (Random I/O):&lt;/strong&gt; Think of a delivery driver having to drive to 100 random houses across a city. The physical disk has to seek and jump around to find specific pages. &lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;The WAL (Sequential I/O):&lt;/strong&gt; Think of delivering 100 pizzas to the same office building. The WAL is an append-only file. Postgres just drops the data at the very end of the file in one continuous, lightning-fast motion.&lt;/li&gt;
&lt;/ul&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%2F6lhr3aycr52meu3rcxig.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%2F6lhr3aycr52meu3rcxig.png" alt="Sequential vs Random IO" width="799" height="339"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Sequential I/O (the WAL) requires zero jumping around, making it exponentially faster than Random I/O (Table updates).&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Later, a background process called the &lt;strong&gt;Checkpointer&lt;/strong&gt; wakes up, takes all the "dirty" pages from RAM, and quietly does the slow work of overwriting the actual table files on the SSD. Once finished, it updates the WAL with a "Checkpoint." &lt;/p&gt;

&lt;p&gt;If the server crashes, Postgres reboots, looks at the last Checkpoint in the WAL, and reapplies all the missing changes to RAM. Zero data loss. &lt;/p&gt;




&lt;h2&gt;
  
  
  3. The MVCC Plot Twist (How UPDATE Works)
&lt;/h2&gt;

&lt;p&gt;Five seconds later, Driver 105 moves. We run an &lt;code&gt;UPDATE&lt;/code&gt; query. &lt;/p&gt;

&lt;p&gt;Here is the biggest secret in Postgres: &lt;strong&gt;It never updates a row in place.&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;If it overwrote the row, and an analytics query was reading that exact row at the same millisecond, the database would lock up. Instead, Postgres uses &lt;strong&gt;MVCC (Multi-Version Concurrency Control)&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;When you &lt;code&gt;UPDATE&lt;/code&gt;, Postgres:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Leaves the old location exactly where it is, but tags it internally as a "Dead Tuple" (using a hidden &lt;code&gt;xmax&lt;/code&gt; column).&lt;/li&gt;
&lt;li&gt;Treats the update like a brand new &lt;code&gt;INSERT&lt;/code&gt;, finding new space and writing the new location.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both versions exist simultaneously! This is why readers never block writers in Postgres. (A background janitor called &lt;code&gt;VACUUM&lt;/code&gt; eventually comes by and deletes the dead rows to save space).&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Scaling Up: Physical Replication
&lt;/h2&gt;

&lt;p&gt;QuickBite goes viral. One database can't handle the read traffic. We add a "Slave" (Read Replica) database. How do we keep it in sync? &lt;/p&gt;

&lt;p&gt;We use &lt;strong&gt;Physical Replication&lt;/strong&gt; (&lt;code&gt;wal_level = replica&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;The Master database doesn't send SQL queries to the Slave. It simply streams the raw binary WAL files over the network. The Slave receives those raw 8KB block changes, saves them to its own disk, and continuously runs in a state of "Crash Recovery"—replaying the Master's disk bytes into its own memory. &lt;/p&gt;

&lt;p&gt;It’s an exact, byte-for-byte clone. &lt;/p&gt;




&lt;h2&gt;
  
  
  5. Analytics &amp;amp; CDC: The Logical Pipeline
&lt;/h2&gt;

&lt;p&gt;Eventually, the business team wants to run massive OLAP analytics ("What is the average delivery time per region this month?"). If they run that on the Postgres Replica, the CPU will spike and crash the app. We need to move the data to a columnar database like &lt;strong&gt;ClickHouse&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We can't use physical replication because ClickHouse doesn't understand Postgres's raw disk bytes. We need &lt;strong&gt;Change Data Capture (CDC)&lt;/strong&gt; using a tool like &lt;strong&gt;Debezium&lt;/strong&gt; and &lt;strong&gt;Kafka&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Flow:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Logical Decoding:&lt;/strong&gt; We change Postgres to &lt;code&gt;wal_level = logical&lt;/code&gt;. Now, instead of just raw disk bytes, Postgres includes table and row metadata in the WAL.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;The Bookmark:&lt;/strong&gt; Debezium connects to Postgres and creates a &lt;strong&gt;Replication Slot&lt;/strong&gt;. This is a permanent bookmark on the server tracking exactly which WAL logs Debezium has consumed.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;The Stream:&lt;/strong&gt; Postgres translates the WAL into a logical format and streams it to Debezium. Debezium formats it into a beautiful JSON payload (with &lt;code&gt;before&lt;/code&gt; and &lt;code&gt;after&lt;/code&gt; states) and pushes it to Kafka.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;The Ingestion:&lt;/strong&gt; ClickHouse continuously consumes this Kafka topic, instantly making the data available for analytics.&lt;/li&gt;
&lt;/ol&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%2Fhv65laj6q98qgfjqgjzj.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%2Fhv65laj6q98qgfjqgjzj.png" alt="Debezium Kafka Pipeline" width="799" height="380"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The Disaster Scenarios:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;What if Debezium is added 2 years late?&lt;/strong&gt; Debezium first locks the table, runs a massive &lt;code&gt;SELECT *&lt;/code&gt; to snapshot the historical data into Kafka, and &lt;em&gt;then&lt;/em&gt; seamlessly switches to reading the live WAL stream. &lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;What if Kafka crashes?&lt;/strong&gt; Debezium stops sending "ACKs" (acknowledgments) to Postgres. Postgres looks at the Replication Slot and says, &lt;em&gt;"Debezium is stuck. I will hoard the WAL files on my hard drive until it comes back."&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;What if Kafka is down for a week?&lt;/strong&gt; To prevent the Postgres hard drive from reaching 100% and crashing the entire food delivery app, we configure &lt;code&gt;max_slot_wal_keep_size&lt;/code&gt;. Once the hoarded WAL hits this limit (e.g., 50GB), Postgres deletes the Replication Slot to save itself. We lose the CDC pipeline temporarily, but the main database survives!&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The next time you write a simple &lt;code&gt;INSERT&lt;/code&gt;, take a moment to appreciate the incredible, highly-orchestrated dance happening under the hood. From the Buffer Manager juggling RAM, to the WAL ensuring durability, to replication slots enabling real-time global analytics—PostgreSQL is an absolute engineering marvel.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Have you ever brought down a database with a bad replication setup? Let me know in the comments!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>database</category>
      <category>kafka</category>
      <category>dataengineering</category>
    </item>
    <item>
      <title>Struggling to make LangGraph agents work in a strict NestJS &amp; TypeScript environment? I spent my weekend fighting through the documentation gap so you don't have to.
Here’s the complete guide to building a modular, tool-using AI agent from scratch.</title>
      <dc:creator>Akhil</dc:creator>
      <pubDate>Sat, 04 Oct 2025 23:30:08 +0000</pubDate>
      <link>https://dev.to/d_akhil_kumar/struggling-to-make-langgraph-agents-work-in-a-strict-nestjs-typescript-environment-i-spent-my-1bhf</link>
      <guid>https://dev.to/d_akhil_kumar/struggling-to-make-langgraph-agents-work-in-a-strict-nestjs-typescript-environment-i-spent-my-1bhf</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/d_akhil_kumar/how-to-build-a-modular-ai-agent-with-langgraph-in-nestjs-typescript-3m9f" class="crayons-story__hidden-navigation-link"&gt;How to Build a Modular AI Agent with LangGraph in NestJS &amp;amp; TypeScript&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/d_akhil_kumar" class="crayons-avatar  crayons-avatar--l  "&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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3480151%2F77fde976-0f63-40ea-81bf-134f40cc4b42.png" alt="d_akhil_kumar profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/d_akhil_kumar" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Akhil
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Akhil
                
              
              &lt;div id="story-author-preview-content-2892864" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/d_akhil_kumar" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3480151%2F77fde976-0f63-40ea-81bf-134f40cc4b42.png" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Akhil&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/d_akhil_kumar/how-to-build-a-modular-ai-agent-with-langgraph-in-nestjs-typescript-3m9f" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Oct 4 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/d_akhil_kumar/how-to-build-a-modular-ai-agent-with-langgraph-in-nestjs-typescript-3m9f" id="article-link-2892864"&gt;
          How to Build a Modular AI Agent with LangGraph in NestJS &amp;amp; TypeScript
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/typescript"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;typescript&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/nestjs"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;nestjs&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/langgraph"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;langgraph&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/node"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;node&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/d_akhil_kumar/how-to-build-a-modular-ai-agent-with-langgraph-in-nestjs-typescript-3m9f" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;6&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/d_akhil_kumar/how-to-build-a-modular-ai-agent-with-langgraph-in-nestjs-typescript-3m9f#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              &lt;span class="hidden s:inline"&gt;Add&amp;nbsp;Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            6 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>typescript</category>
      <category>nestjs</category>
      <category>langgraph</category>
      <category>node</category>
    </item>
    <item>
      <title>How to Build a Modular AI Agent with LangGraph in NestJS &amp; TypeScript</title>
      <dc:creator>Akhil</dc:creator>
      <pubDate>Sat, 04 Oct 2025 23:14:01 +0000</pubDate>
      <link>https://dev.to/d_akhil_kumar/how-to-build-a-modular-ai-agent-with-langgraph-in-nestjs-typescript-3m9f</link>
      <guid>https://dev.to/d_akhil_kumar/how-to-build-a-modular-ai-agent-with-langgraph-in-nestjs-typescript-3m9f</guid>
      <description>&lt;p&gt;The world of AI is buzzing with the concept of autonomous agents. From simple tool-users to complex systems that can plan and execute tasks, developers are eager to build the next generation of intelligent applications. LangChain, with its powerful LangGraph library, has emerged as a key player in this space.&lt;/p&gt;

&lt;p&gt;However, if you're a developer working in the Node.js ecosystem with professional frameworks like NestJS and a love for strict TypeScript, you might have felt a bit lost. The documentation, while excellent, often focuses on Python or simpler JavaScript examples, leaving us to piece together the patterns for a robust, modular, and type-safe implementation.&lt;/p&gt;

&lt;p&gt;After a journey of trial, error, and discovery, I'm here to share the guide I wish I had. We're going to build a real, working AI agent from scratch, strictly following the latest LangGraph patterns in a NestJS environment.&lt;/p&gt;




&lt;h3&gt;
  
  
  What We're Building: Triage Panda 🐼
&lt;/h3&gt;

&lt;p&gt;To make this practical, we'll use our agent, &lt;strong&gt;Triage Panda&lt;/strong&gt;, as the example. Its job is to act as an autonomous software engineering assistant that automatically triages new GitHub issues. When a new issue is created, the agent reads it, decides on appropriate labels, uses tools to apply them, and posts a comment back to GitHub.&lt;/p&gt;

&lt;p&gt;This requires a stateful, multi-step workflow...a perfect use case for LangGraph.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architectural Blueprint: Engine vs. Specialist 🏗️
&lt;/h2&gt;

&lt;p&gt;The key to building a scalable and reusable agent is to &lt;strong&gt;separate the generic logic from the task-specific logic&lt;/strong&gt;. We'll achieve this with a two-part architecture:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;The Generic &lt;code&gt;AgentService&lt;/code&gt; (The Engine):&lt;/strong&gt; This is our core, reusable class. It knows &lt;em&gt;how&lt;/em&gt; to run an agentic loop (Think -&amp;gt; Act -&amp;gt; Observe) but knows nothing about GitHub, Slack, or any other specific task. It's a pure orchestrator.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;The Specialist &lt;code&gt;GithubAgentService&lt;/code&gt; (The Mission Commander):&lt;/strong&gt; This class knows &lt;em&gt;what&lt;/em&gt; the mission is. It's responsible for preparing the specific tools (e.g., GitHub tools) and prompts for a given task, and then telling the generic Engine to start.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This design allows us to easily add a &lt;code&gt;GitlabAgentService&lt;/code&gt; or &lt;code&gt;SlackAgentService&lt;/code&gt; later without ever touching our core agent engine.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1: Building the Tools of the Trade 🛠️
&lt;/h2&gt;

&lt;p&gt;An agent is only as good as its tools. Using the latest &lt;code&gt;tool&lt;/code&gt; helper from &lt;code&gt;@langchain/core/tools&lt;/code&gt; and Zod for schema validation, we can define our tools in a clean and type-safe way. We'll create a &lt;code&gt;GithubService&lt;/code&gt; class to act as a factory for these.&lt;/p&gt;

&lt;p&gt;Here’s how we define a tool to fetch a GitHub issue. The &lt;code&gt;description&lt;/code&gt; is the most critical part, as it's the instruction manual the LLM reads to decide when to use the tool.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// backend/src/modules/github/domain/services/github.service.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;tool&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@langchain/core/tools&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GithubService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;getTools&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;DynamicStructuredTool&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// The `tool` helper function from LangChain is used to wrap any function&lt;/span&gt;
    &lt;span class="c1"&gt;// into a tool that the AI agent can understand and call.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;getIssueByNumberTool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="c1"&gt;// The first argument is the actual async function to execute when the tool is called.&lt;/span&gt;
      &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;issueNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// ... findIssueByNumber is the custom class method&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;issue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findIssueByNumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
          &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;issueNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="c1"&gt;// The second argument is a configuration object that "describes" the tool to the LLM.&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;get_github_issue_by_number&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Fetches details for a single GitHub issue. Use this as the first step to get the context of the issue.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
          &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;The owner of the repository.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;The name of the repository.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
          &lt;span class="na"&gt;issueNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;number&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;The number of the issue to fetch.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;}),&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// ... definitions for postCommentTool and addLabelsTool&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;getIssueByNumberTool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="cm"&gt;/* ...other tools */&lt;/span&gt;&lt;span class="p"&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;
  
  
  Step 2: The Reusable Agent Engine (&lt;code&gt;AgentService&lt;/code&gt;) ⚙️
&lt;/h2&gt;

&lt;p&gt;This is the core of our architecture. It's a generic service that takes any set of tools and messages, compiles a &lt;code&gt;StateGraph&lt;/code&gt; on the fly, and runs the agentic loop until a final answer is produced.&lt;/p&gt;

&lt;p&gt;Notice how this service has no mention of "GitHub"; it's completely reusable.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// backend/src/modules/agent/domain/services/agent.service.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;InternalServerErrorException&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Logger&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@nestjs/common&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ConfigService&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@nestjs/config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ChatGoogleGenerativeAI&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@langchain/google-genai&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AIMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;BaseMessage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@langchain/core/messages&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Tool&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@langchain/core/tools&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;StateGraph&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;MessagesAnnotation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;END&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;START&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@langchain/langgraph&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ToolNode&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@langchain/langgraph/prebuilt&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Runnable&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@langchain/core/runnables&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;AgentResult&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;fullHistory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BaseMessage&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AgentService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;AgentService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="c1"&gt;// ... static properties for model name, etc.&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;configService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ConfigService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Tool&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BaseMessage&lt;/span&gt;&lt;span class="p"&gt;[]):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AgentResult&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;graph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;buildGraph&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;finalState&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;MessagesAnnotation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;State&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;recursionLimit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AgentService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MAX_RECURSION_LIMIT&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lastMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;finalState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;finalState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validateAgentResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lastMessage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;response&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;lastMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;fullHistory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;finalState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&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;private&lt;/span&gt; &lt;span class="nf"&gt;createModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DynamicStructuredTool&lt;/span&gt;&lt;span class="p"&gt;[]):&lt;/span&gt; &lt;span class="nx"&gt;Runnable&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;LLM_API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;get&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;llm.geminiApiKey&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;

    &lt;span class="c1"&gt;// The .bindTools() method attaches our tool definitions to the LLM,&lt;/span&gt;
    &lt;span class="c1"&gt;// allowing it to decide when to call them.&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ChatGoogleGenerativeAI&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AgentService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LLM_MODEL_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AgentService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DEFAULT_TEMPERATURE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LLM_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;bindTools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;buildGraph&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DynamicStructuredTool&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;
    &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Runnable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;StateGraph&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;compile&lt;/span&gt; &lt;span class="nf"&gt;extends &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;infer&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;never&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// The ToolNode is a pre-built node from LangGraph that knows how to execute tools.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;toolNode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ToolNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// This is our "Think" node. It calls the LLM to get the next action.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;callModel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;MessagesAnnotation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;State&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Partial&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;AgentState&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;AIMessage&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// This is our "Router". It evaluates the LLM's response to determine the next step.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;shouldContinue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;MessagesAnnotation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;State&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tools&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;__end__&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;state&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lastMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;AIMessage&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;AgentService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;MAX_MESSAGE_COUNT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;END&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="c1"&gt;// If the LLM's last message contains a tool call, we route to the 'tools' node.&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lastMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tool_calls&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tools&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;END&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;StateGraph&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;MessagesAnnotation&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;agent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;callModel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addNode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tools&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;toolNode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addEdge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;START&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;agent&lt;/span&gt;&lt;span class="dl"&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;addEdge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;tools&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;agent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// After executing tools, always go back to the "agent" to think again.&lt;/span&gt;
      &lt;span class="c1"&gt;// The conditional edge is the router. After the "agent" node, call `shouldContinue`&lt;/span&gt;
      &lt;span class="c1"&gt;// to decide whether to go to the "tools" node or to the special "END" node.&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addConditionalEdges&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;agent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;shouldContinue&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;compile&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;validateAgentResponse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// ... validation logic&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;
  
  
  Step 3: The Specialist (&lt;code&gt;GithubAgentService&lt;/code&gt;) 🐙
&lt;/h2&gt;

&lt;p&gt;With the powerful generic engine in place, our &lt;code&gt;GithubAgentService&lt;/code&gt; becomes incredibly simple and focused. Its only job is to prepare the "mission briefing": get the right tools and craft the right prompt.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// backend/src/modules/github/domain/services/github-agent.service.ts&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Logger&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@nestjs/common&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;BaseMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;HumanMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;SystemMessage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@langchain/core/messages&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;AgentService&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;src/modules/agent/domain/services/agent.service&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;GithubTool&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;src/modules/github/tools/github.tool&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Our tool factory&lt;/span&gt;

&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GithubAgentService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;GithubAgentService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;agentService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AgentService&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// Inject the generic engine&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;startTriage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;issueNumber&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;DynamicStructuredTool&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;GithubAgentService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`Starting triage for &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; #&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;issueNumber&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;systemPrompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`
          You are an expert GitHub issue triaging agent. Your goal is to fully triage the issue provided.
            1. First, call the 'get_github_issue' tool to fetch the issue content.
            2. Analyze the issue's title and body to determine appropriate labels and a helpful summary comment.
            3. Call the 'add_github_labels' and 'post_github_comment' tools to apply your analysis.
            4. Once all tools have been successfully called, respond with a final confirmation message summarizing your actions.
          `&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userPrompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`Please triage issue #&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;issueNumber&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; in the repository &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.`&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BaseMessage&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SystemMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;systemPrompt&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
      &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;HumanMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userPrompt&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;agentService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;invoke&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="nx"&gt;GithubAgentService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;`Triage complete for issue #&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;issueNumber&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. Final response: "&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"`&lt;/span&gt;&lt;span class="p"&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;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;GithubAgentService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="s2"&gt;`Triage failed for issue #&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;issueNumber&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&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;
  
  
  Step 4: The Invoker (&lt;code&gt;GithubService&lt;/code&gt;) 🐙
&lt;/h2&gt;

&lt;p&gt;When it gets triggered by the GitHub webhook URL, its only job is to further invoke the GitHub agent by passing the tools and the initial state.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// backend/src/modules/github/domain/services/github.service.ts&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Injectable&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GithubService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="nx"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Logger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;GithubService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="nf"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="k"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;agentService&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GithubAgentService&lt;/span&gt;&lt;span class="p"&gt;,,&lt;/span&gt; &lt;span class="c1"&gt;// Inject the github agent service&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

    &lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="nf"&gt;handleWebhook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;rawBody&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;GithubWebhookPayload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;GithubWebhook&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verifySignature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rawBody&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;action&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;opened&lt;/span&gt;&lt;span class="dl"&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Ignoring action: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ignored&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;reason&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Action was &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;, not 'opened'`&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;owner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;login&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;repo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;issueNumber&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;issue&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;

    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="s2"&gt;`Webhook processed for issue #&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;issueNumber&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; in &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&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;void&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;agentService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startTriage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nx"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;issueNumber&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getTools&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;processing&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

 &lt;span class="c1"&gt;// getTools method created in step #1&lt;/span&gt;
 &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nf"&gt;getTools&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="nx"&gt;DynamicStructuredTool&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&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;
  
  
  Conclusion &amp;amp; What's Next 🚀
&lt;/h2&gt;

&lt;p&gt;We've successfully built a simple but powerful and most importantly &lt;strong&gt;modular&lt;/strong&gt; AI agent in a professional NestJS and TypeScript environment. This architecture provides a solid foundation for building much more complex applications in the future. By separating the generic engine from the specialist logic, we can easily add new capabilities without rewriting our core workflow.&lt;/p&gt;

&lt;p&gt;In our next blog post, we'll dive into observability. We'll integrate &lt;strong&gt;Langfuse&lt;/strong&gt;, an open-source tracing tool, to visualize our agent's every thought and action, making debugging and analysis a breeze.&lt;/p&gt;

&lt;p&gt;The full source code for our &lt;strong&gt;Triage Panda&lt;/strong&gt; agent can be found on GitHub. Feel free to explore it, open issues (and watch the agent triage them!), and contribute.&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/d-akhil-kumar" rel="noopener noreferrer"&gt;
        d-akhil-kumar
      &lt;/a&gt; / &lt;a href="https://github.com/d-akhil-kumar/triage-panda" rel="noopener noreferrer"&gt;
        triage-panda
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      🐼 AI-Powered GitHub Issue Triage Agent 🤖
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;🐼 triage-panda – AI-Powered GitHub Issue Triage Agent 🤖&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;An autonomous AI agent 🐼 built with &lt;strong&gt;NestJS&lt;/strong&gt; and &lt;strong&gt;LangGraph&lt;/strong&gt; to automatically triage and manage GitHub issues. This project leverages the power of Large Language Models (LLMs) to understand, categorize, prioritize, and comment on new issues, streamlining the workflow for development teams.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;✨ Core Features&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;This agent uses a stateful, cyclical workflow to perform its tasks. When a new issue is created in a configured repository, the agent will:&lt;/p&gt;


&lt;ol&gt;

&lt;li&gt;

&lt;strong&gt;Webhook Trigger:&lt;/strong&gt; Automatically activate when a new issue is created via a GitHub webhook.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Multi-Step Tool Use:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;First, it fetches the issue content using the &lt;code&gt;get_github_issue_by_number&lt;/code&gt; tool.&lt;/li&gt;
&lt;li&gt;It analyzes this content to decide on appropriate labels and a summary comment.&lt;/li&gt;
&lt;li&gt;Finally, it uses the &lt;code&gt;add_github_labels&lt;/code&gt; and &lt;code&gt;post_github_comment&lt;/code&gt; tools to apply its analysis back to GitHub.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;AI-Powered Decisions:&lt;/strong&gt; Uses Google's Gemini model to reason about the issue and decide which tools to use…&lt;/li&gt;

&lt;/ol&gt;&lt;/div&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/d-akhil-kumar/triage-panda" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;





&lt;p&gt;Happy building! Let me know your thoughts, questions, and what you're building with this stack in the comments below.&lt;/p&gt;

</description>
      <category>typescript</category>
      <category>nestjs</category>
      <category>langgraph</category>
      <category>node</category>
    </item>
    <item>
      <title>🚀 Will deleting the package-lock.json makes me cool 😎?</title>
      <dc:creator>Akhil</dc:creator>
      <pubDate>Wed, 17 Sep 2025 16:49:55 +0000</pubDate>
      <link>https://dev.to/d_akhil_kumar/will-deleting-the-package-lockjson-makes-me-cool--374i</link>
      <guid>https://dev.to/d_akhil_kumar/will-deleting-the-package-lockjson-makes-me-cool--374i</guid>
      <description>&lt;h2&gt;
  
  
  🚀 npm Dependencies Demystified: The Great &lt;code&gt;package-lock&lt;/code&gt; Mystery
&lt;/h2&gt;



&lt;p&gt;As developers, we've all been there: a teammate runs &lt;code&gt;npm install&lt;/code&gt; and suddenly their app breaks, even though &lt;em&gt;it works on my machine™&lt;/em&gt; . It's a classic programming horror story. The villain? Often an unexpected plot twist in &lt;strong&gt;dependency version resolution&lt;/strong&gt;, brought to you by disrespecting the &lt;strong&gt;&lt;code&gt;package-lock.json&lt;/code&gt;&lt;/strong&gt; file.&lt;/p&gt;

&lt;p&gt;Let's pull back the curtain and solve this mystery once and for all. 👇&lt;/p&gt;




&lt;h2&gt;
  
  
  📦 Semver Basics: A Tale of Three Symbols (&lt;code&gt;^&lt;/code&gt;, &lt;code&gt;~&lt;/code&gt;, and The Silent')
&lt;/h2&gt;

&lt;p&gt;Think of your dependencies like a subscription service for code. The version number is your subscription plan.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Exact version (&lt;code&gt;"express": "4.17.1"&lt;/code&gt;)&lt;/strong&gt;
You're signing up for a &lt;strong&gt;fixed plan&lt;/strong&gt;. No changes, ever. You'll always get &lt;code&gt;4.17.1&lt;/code&gt; and nothing else. It's the most stable, but you'll miss out on security patches and bug fixes unless you manually update.&lt;/li&gt;
&lt;/ul&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Tilde &lt;code&gt;~&lt;/code&gt; (Patch Updates)&lt;/strong&gt; 🩹&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"express"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"~4.17.1"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;This is like a &lt;strong&gt;"same minor version, but give me the latest bug fixes"&lt;/strong&gt; plan. You'll get any new patch versions, but you're staying within the &lt;code&gt;4.17.x&lt;/code&gt; family. So, &lt;code&gt;4.17.1&lt;/code&gt; could update to &lt;code&gt;4.17.3&lt;/code&gt;, but it will &lt;em&gt;never&lt;/em&gt; jump to &lt;code&gt;4.18.0&lt;/code&gt;.&lt;br&gt;
&lt;br&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Caret &lt;code&gt;^&lt;/code&gt; (Minor + Patch Updates)&lt;/strong&gt; 🚀&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"express"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^4.17.1"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;This is the &lt;strong&gt;"latest and greatest, but within the same major version"&lt;/strong&gt; plan. You get all the new features and fixes as long as they don't break compatibility. &lt;code&gt;4.17.1&lt;/code&gt; could update to &lt;code&gt;4.18.2&lt;/code&gt; or &lt;code&gt;4.19.0&lt;/code&gt;, but it will never jump to a scary &lt;code&gt;5.0.0&lt;/code&gt; (which could have breaking changes).&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;



&lt;p&gt;So remember:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;~&lt;/code&gt; = "Patch me up!"&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;^&lt;/code&gt; = "Rocket me to the next minor version!"&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🔒 The &lt;code&gt;package-lock.json&lt;/code&gt;: Your Project's DNA
&lt;/h2&gt;

&lt;p&gt;When you run &lt;code&gt;npm install&lt;/code&gt;, npm takes a snapshot of your entire dependency family tree, including every single nested version. This snapshot is saved in the &lt;strong&gt;&lt;code&gt;package-lock.json&lt;/code&gt;&lt;/strong&gt; file.&lt;/p&gt;



&lt;p&gt;Imagine &lt;code&gt;package.json&lt;/code&gt; is your grocery list: "I need milk (&lt;code&gt;^1.0.0&lt;/code&gt;) and bread (&lt;code&gt;~2.0.0&lt;/code&gt;)".&lt;br&gt;
The &lt;code&gt;package-lock.json&lt;/code&gt; is the &lt;strong&gt;receipt&lt;/strong&gt; from your last shopping trip: "I bought &lt;code&gt;Milk@1.2.5&lt;/code&gt; from this specific brand, and &lt;code&gt;Bread@2.0.3&lt;/code&gt; from that other brand."&lt;/p&gt;



&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;With &lt;code&gt;package-lock.json&lt;/code&gt;:&lt;/strong&gt; Everyone on your team gets the &lt;em&gt;exact same&lt;/em&gt; groceries from the last trip. Predictability and consistency are guaranteed.&lt;/li&gt;
&lt;/ul&gt;



&lt;ul&gt;
&lt;li&gt;❌ &lt;strong&gt;Without &lt;code&gt;package-lock.json&lt;/code&gt;:&lt;/strong&gt; Every time you go to the store, you'll get the "latest milk" and "latest bread," which might be different brands (different versions) from what your teammates got. This is where the &lt;em&gt;"it worked on my machine"&lt;/em&gt; ghost appears.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  👻 An Example Scenario: The Disappearing Dependency Trick
&lt;/h2&gt;

&lt;p&gt;Let's say your &lt;code&gt;package.json&lt;/code&gt; has:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"A"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^1.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"B"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^2.0.0"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;First Install:&lt;/strong&gt;&lt;br&gt;
You run &lt;code&gt;npm install&lt;/code&gt;. At that moment, &lt;code&gt;A@1.0.0&lt;/code&gt; needs a dependency called &lt;code&gt;X&lt;/code&gt;, which resolves to &lt;code&gt;X@1.0.5&lt;/code&gt;. Your other dependency, &lt;code&gt;B@2.0.0&lt;/code&gt;, also needs &lt;code&gt;X&lt;/code&gt;, but it resolves to &lt;code&gt;X@2.0.3&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Your dependency tree looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A → X@1.0.5
B → X@2.0.3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This precise, exact tree is locked into your &lt;code&gt;package-lock.json&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A Week Later (The Mystery Begins):&lt;/strong&gt;&lt;br&gt;
New versions of &lt;code&gt;X&lt;/code&gt; are released: &lt;code&gt;X@1.0.6&lt;/code&gt; and &lt;code&gt;X@2.0.4&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you were to &lt;strong&gt;delete your &lt;code&gt;package-lock.json&lt;/code&gt;&lt;/strong&gt; and run &lt;code&gt;npm install&lt;/code&gt; again, npm will ignore your "receipt" and get the latest groceries according to your "shopping list" (&lt;code&gt;package.json&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Your new tree:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;A → X@1.0.6
B → X@2.0.4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Same &lt;code&gt;package.json&lt;/code&gt;, different result!&lt;/strong&gt; The app might now be broken because a new version of &lt;code&gt;X&lt;/code&gt; introduced a bug or a breaking change. This is why deleting the lockfile is a developer's version of a high-risk stunt.&lt;/p&gt;




&lt;h2&gt;
  
  
  🤝 The Golden Rules: Stay Sane, Stay Consistent
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Always commit &lt;code&gt;package-lock.json&lt;/code&gt; to your repo.&lt;/strong&gt; This is the secret handshake that guarantees everyone on the team is on the same page.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Use &lt;code&gt;npm ci&lt;/code&gt; in CI/CD pipelines.&lt;/strong&gt; &lt;code&gt;ci&lt;/code&gt; stands for "clean install." It's like a drill sergeant: it installs &lt;em&gt;exactly&lt;/em&gt; what's in your lockfile and fails if the two don't match. No surprises, no funny business.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Treat your &lt;code&gt;package.json&lt;/code&gt; and &lt;code&gt;package-lock.json&lt;/code&gt; as a single unit.&lt;/strong&gt; They are yin and yang, the plan and the result.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  🎯 TL;DR: The Final Verdict
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;^&lt;/code&gt;&lt;/strong&gt; → "I want the latest non-breaking features."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;~&lt;/code&gt;&lt;/strong&gt; → "I just want bug fixes for this specific version."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;package-lock.json&lt;/code&gt;&lt;/strong&gt; → "I want to replicate this exact installation on any machine, anywhere, anytime."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deleting the lockfile&lt;/strong&gt; → "I like living dangerously and want to risk breaking my app for fun."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, will deleting the &lt;code&gt;package.lock&lt;/code&gt; file make you cool? Not really. It just makes you a chaotic agent of uncertainty. Keep your lockfile. Use &lt;code&gt;npm ci&lt;/code&gt;. And save yourself from the eternal headache of &lt;em&gt;"but it worked yesterday!"&lt;/em&gt; 😅&lt;/p&gt;




</description>
      <category>npm</category>
      <category>node</category>
      <category>javascript</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Managing Multiple GitHub Accounts (Work + Personal) on the Same Machine</title>
      <dc:creator>Akhil</dc:creator>
      <pubDate>Thu, 04 Sep 2025 18:46:08 +0000</pubDate>
      <link>https://dev.to/d_akhil_kumar/managing-multiple-github-accounts-work-personal-on-the-same-machine-7o2</link>
      <guid>https://dev.to/d_akhil_kumar/managing-multiple-github-accounts-work-personal-on-the-same-machine-7o2</guid>
      <description>&lt;h2&gt;
  
  
  Managing Multiple GitHub Accounts
&lt;/h2&gt;

&lt;p&gt;As developers, it’s common to juggle &lt;strong&gt;two GitHub accounts&lt;/strong&gt; — one for work (e.g., &lt;code&gt;akhil@&amp;lt;COMPANY_NAME&amp;gt;.com&lt;/code&gt;) and one for personal projects (e.g., &lt;code&gt;akhil@gmail.com&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;The challenge?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Git commits end up with the wrong email.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;git push&lt;/code&gt; gets rejected because the wrong GitHub account is used.&lt;/li&gt;
&lt;li&gt;Constantly logging in/out is frustrating.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this post, I’ll walk you through how to &lt;strong&gt;configure Git + SSH to cleanly separate accounts&lt;/strong&gt; so that your work and personal projects never interfere with each other.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔎 The Problem I Faced
&lt;/h2&gt;

&lt;p&gt;On my work laptop:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Git was configured with my &lt;strong&gt;work email&lt;/strong&gt; globally.&lt;/li&gt;
&lt;li&gt;My repos were cloned using &lt;strong&gt;HTTPS&lt;/strong&gt;, which always authenticated via my company GitHub account.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When I tried pushing code to my &lt;strong&gt;personal GitHub repo&lt;/strong&gt;, I got this error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;remote: Permission to d-akhil-kumar/log-panda.git denied to akhil-kumar-company-git.
fatal: unable to access 'https://github.com/d-akhil-kumar/log-panda.git/': The requested URL returned error: 403
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GitHub was picking the wrong account (&lt;code&gt;akhil-kumar-company-git&lt;/code&gt; from work), while I needed my personal account (&lt;code&gt;d-akhil-kumar&lt;/code&gt;).&lt;/p&gt;




&lt;h2&gt;
  
  
  ✅ The Solution: Separate SSH Keys and Git Configs
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Generate a Personal SSH Key
&lt;/h3&gt;

&lt;p&gt;I already had a company (default) SSH key (&lt;code&gt;~/.ssh/id_rsa&lt;/code&gt;), so I created a new one for my personal account:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh-keygen &lt;span class="nt"&gt;-t&lt;/span&gt; ed25519 &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"your-personal-email@example.com"&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; ~/.ssh/id_ed25519_personal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now I have:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;~/.ssh/id_rsa&lt;/code&gt; → company key&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;~/.ssh/id_ed25519_personal&lt;/code&gt; → personal key&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  2. Add Keys to the SSH Agent
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;ssh-agent &lt;span class="nt"&gt;-s&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
ssh-add ~/.ssh/id_rsa              &lt;span class="c"&gt;# company&lt;/span&gt;
ssh-add ~/.ssh/id_ed25519_personal &lt;span class="c"&gt;# personal&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  3. Add the Public Key to GitHub
&lt;/h3&gt;

&lt;p&gt;Copy the personal key:&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;cat&lt;/span&gt; ~/.ssh/id_ed25519_personal.pub
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Go to &lt;strong&gt;GitHub → Settings → SSH and GPG keys → New SSH key&lt;/strong&gt; and paste it.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. Configure SSH for Multiple Accounts
&lt;/h3&gt;

&lt;p&gt;Edit (or create) &lt;code&gt;~/.ssh/config&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ssh"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Company GitHub&lt;/span&gt;
&lt;span class="k"&gt;Host&lt;/span&gt; github.com-company
    &lt;span class="k"&gt;HostName&lt;/span&gt; github.com
    &lt;span class="k"&gt;User&lt;/span&gt; git
    &lt;span class="k"&gt;IdentityFile&lt;/span&gt; ~/.ssh/id_rsa

&lt;span class="c1"&gt;# Personal GitHub&lt;/span&gt;
&lt;span class="k"&gt;Host&lt;/span&gt; github.com-personal
    &lt;span class="k"&gt;HostName&lt;/span&gt; github.com
    &lt;span class="k"&gt;User&lt;/span&gt; git
    &lt;span class="k"&gt;IdentityFile&lt;/span&gt; ~/.ssh/id_ed25519_personal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This tells SSH which key to use based on the alias (&lt;code&gt;github.com-company&lt;/code&gt; or &lt;code&gt;github.com-personal&lt;/code&gt;).&lt;/p&gt;




&lt;h3&gt;
  
  
  5. Update Git Remotes
&lt;/h3&gt;

&lt;p&gt;Inside a &lt;strong&gt;personal repo&lt;/strong&gt;, update the remote URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git remote set-url origin git@github.com-personal:your-username/your-repo.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a &lt;strong&gt;company repo&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git remote set-url origin git@github.com-company:company-org/repo-name.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git remote &lt;span class="nt"&gt;-v&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  6. Test the Connection
&lt;/h3&gt;

&lt;p&gt;For personal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-T&lt;/span&gt; git@github.com-personal
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For company:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh &lt;span class="nt"&gt;-T&lt;/span&gt; git@github.com-company
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both should greet you with the correct account username 🎉&lt;/p&gt;




&lt;h3&gt;
  
  
  7. Set Git Commit Identity Per Repo
&lt;/h3&gt;

&lt;p&gt;Inside a &lt;strong&gt;personal repo&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git config user.name &lt;span class="s2"&gt;"Your Name"&lt;/span&gt;
git config user.email &lt;span class="s2"&gt;"your-personal-email@example.com"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inside a &lt;strong&gt;company repo&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git config user.name &lt;span class="s2"&gt;"Your Name"&lt;/span&gt;
git config user.email &lt;span class="s2"&gt;"your-work-email@&amp;lt;COMPANY_NAME&amp;gt;.com"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, commits show up under the right account.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚡ Bonus: Auto-Switch Identities
&lt;/h2&gt;

&lt;p&gt;If you keep repos in separate folders (&lt;code&gt;~/work/&lt;/code&gt; and &lt;code&gt;~/personal/&lt;/code&gt;), Git can auto-switch your email:&lt;/p&gt;

&lt;p&gt;Edit &lt;code&gt;~/.gitconfig&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[user]&lt;/span&gt;
    &lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;Your Name&lt;/span&gt;
    &lt;span class="py"&gt;email&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;your-personal-email@example.com&lt;/span&gt;

&lt;span class="nn"&gt;[includeIf "gitdir:~/work/"]&lt;/span&gt;
    &lt;span class="py"&gt;path&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;~/.gitconfig-company&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create &lt;code&gt;~/.gitconfig-company&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight ini"&gt;&lt;code&gt;&lt;span class="nn"&gt;[user]&lt;/span&gt;
    &lt;span class="py"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;Your Name&lt;/span&gt;
    &lt;span class="py"&gt;email&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;your-work-email@&amp;lt;COMPANY_NAME&amp;gt;.com&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you never need to run &lt;code&gt;git config&lt;/code&gt; manually again. 🚀&lt;/p&gt;




&lt;h2&gt;
  
  
  🎯 Final Result
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Personal repos → push with &lt;strong&gt;personal GitHub account&lt;/strong&gt; and &lt;strong&gt;personal email&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Company repos → push with &lt;strong&gt;company GitHub account&lt;/strong&gt; and &lt;strong&gt;company email&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;No more 403 errors or wrong commit authorship&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🙌 Conclusion
&lt;/h2&gt;

&lt;p&gt;Managing multiple GitHub accounts doesn’t have to be painful.&lt;br&gt;
With separate SSH keys and smart Git config, you can &lt;strong&gt;seamlessly switch between personal and work repos&lt;/strong&gt; without conflicts.&lt;/p&gt;




&lt;p&gt;💡 &lt;em&gt;Have you set up multiple GitHub accounts on your machine? Did you use SSH, HTTPS, or another trick? Let me know in the comments!&lt;/em&gt;&lt;/p&gt;

</description>
      <category>git</category>
      <category>github</category>
      <category>productivity</category>
    </item>
  </channel>
</rss>
