<?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: Subhendu Das</title>
    <description>The latest articles on DEV Community by Subhendu Das (@sumaninster).</description>
    <link>https://dev.to/sumaninster</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%2F4055832%2Fe0e1fcc3-e36e-4f90-8814-c6aef548f373.jpg</url>
      <title>DEV Community: Subhendu Das</title>
      <link>https://dev.to/sumaninster</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sumaninster"/>
    <language>en</language>
    <item>
      <title>Client Management</title>
      <dc:creator>Subhendu Das</dc:creator>
      <pubDate>Sat, 15 Aug 2026 13:36:01 +0000</pubDate>
      <link>https://dev.to/sumaninster/client-management-109p</link>
      <guid>https://dev.to/sumaninster/client-management-109p</guid>
      <description>&lt;h2&gt;
  
  
  Introduction to AI-Powered Client Management
&lt;/h2&gt;

&lt;p&gt;As a small business owner in India, managing customer conversations across multiple channels can be a daunting task. This is where GoSumo, an AI-powered client management platform, comes in. In this spotlight, we'll take a deep look at one of its key features: smart routing and automated responses.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: Manual Customer Conversation Management
&lt;/h2&gt;

&lt;p&gt;Manual management of customer conversations can lead to delays, missed messages, and a poor customer experience. With the rise of multiple messaging channels such as WhatsApp, Instagram, SMS, Web Chat, and Email, it's becoming increasingly difficult for small businesses to keep up.&lt;/p&gt;

&lt;h2&gt;
  
  
  How GoSumo Solves the Problem
&lt;/h2&gt;

&lt;p&gt;GoSumo's AI-driven interface allows small businesses to handle customer conversations across all these channels from a single platform. With smart routing, incoming messages are automatically directed to the right agent or queue, ensuring that customers receive timely and relevant responses. Automated responses further enhance the customer experience by providing instant answers to common queries.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it Looks Like to Use
&lt;/h2&gt;

&lt;p&gt;Using GoSumo, small businesses can streamline their customer conversation management process. The platform's multi-tenant support ensures that businesses can manage multiple clients or brands from a single interface. With features like smart routing and automated responses, businesses can focus on providing personalized support to their customers, rather than getting bogged down in manual message management.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical Details
&lt;/h2&gt;

&lt;p&gt;GoSumo is built using a range of technologies, including NestJS, TypeScript, Next.js, PostgreSQL, Redis, BullMQ, Prisma, Qdrant, and OpenRouter AI. The platform's architecture is designed to provide a scalable and reliable solution for small businesses. Recent updates to the platform have focused on improving performance, security, and observability, with features like correlation IDs and bounded web-chat sockets.&lt;/p&gt;

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

&lt;p&gt;GoSumo's AI-powered client management platform is designed to help small businesses in India manage customer conversations more efficiently. By providing a single interface for multiple channels and leveraging smart routing and automated responses, GoSumo enables businesses to deliver a better customer experience and focus on growth.&lt;/p&gt;

</description>
      <category>gosumo</category>
      <category>aiknol</category>
      <category>india</category>
    </item>
    <item>
      <title>Hardening Document Extraction in a NestJS CDSS</title>
      <dc:creator>Subhendu Das</dc:creator>
      <pubDate>Sat, 15 Aug 2026 13:35:01 +0000</pubDate>
      <link>https://dev.to/sumaninster/hardening-document-extraction-in-a-nestjs-cdss-4jie</link>
      <guid>https://dev.to/sumaninster/hardening-document-extraction-in-a-nestjs-cdss-4jie</guid>
      <description>&lt;h2&gt;
  
  
  Latency and Failure Modes in Clinical AI Pipelines
&lt;/h2&gt;

&lt;p&gt;In a clinical decision support system (CDSS), document ingestion forms the base of the reasoning pipeline. When a clinician uploads an unstructured record, scanned note, or lab report, the system must parse the text, invoke medical AI agents via an LLM gateway (such as OpenRouter), extract clinical entities, and update the patient's longitudinal record in PostgreSQL.&lt;/p&gt;

&lt;p&gt;In early iterations of this pipeline, document extraction suffered from three distinct architectural bottlenecks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Network Overhead on LLM Invocations:&lt;/strong&gt; Every extraction prompt initiated a new connection, paying the latency cost of a fresh TLS handshake per agent call.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ambiguous Parsing States:&lt;/strong&gt; Network timeouts and schema mismatches returned empty payloads that the backend could not differentiate from genuinely empty documents.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fragile Orchestration:&lt;/strong&gt; An unhandled exception in an individual extraction agent halted execution across the entire reasoning case, while interrupted ingestion jobs left documents in a permanent "reading" state.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Recent updates to Documedic resolve these issues by introducing persistent connection pools, explicit ingestion state transitions, and isolated agent execution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Persistent LLM Connection Pooling in NestJS
&lt;/h2&gt;

&lt;p&gt;To minimize round-trip latency when dispatching prompts to OpenRouter, the NestJS extraction service now maintains a persistent agent pool with keep-alive enabled, avoiding repeated TLS negotiations.&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="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;OnModuleDestroy&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;Agent&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;undici&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;OpenRouterClientService&lt;/span&gt; &lt;span class="k"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;OnModuleDestroy&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;dispatcher&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;Agent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;keepAliveTimeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;keepAliveMaxTimeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="nx"&gt;_000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;connections&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;queryAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prompt&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;model&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="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://openrouter.ai/api/v1/chat/completions&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="na"&gt;method&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;dispatcher&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;dispatcher&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;headers&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;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`Bearer &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;OPENROUTER_API_KEY&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Content-Type&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;application/json&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="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&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;model&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="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;prompt&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="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;onModuleDestroy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dispatcher&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;destroy&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;p&gt;Graceful shutdown hooks ensure that connection pools drain cleanly on service restarts rather than leaking open sockets or leaving pending requests unresolved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Differentiating Empty Documents from Extraction Failures
&lt;/h2&gt;

&lt;p&gt;A critical failure mode in clinical document processing is the silent dropping of data. If an OCR scanner yields unparseable text or an LLM call fails downstream, treating the result as an empty document leads to missing diagnoses in the patient chart.&lt;/p&gt;

&lt;p&gt;Documedic now enforces strict status checks across the PostgreSQL ingestion schema. The document pipeline models state transitions through distinct terminal states:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TYPE&lt;/span&gt; &lt;span class="n"&gt;document_status&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="nb"&gt;ENUM&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s1"&gt;'pending'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'extracting'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'completed'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'empty_content'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="s1"&gt;'extraction_failed'&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When processing incoming scans:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Documents with zero detected text tokens are classified as &lt;code&gt;empty_content&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Agent exceptions or API transport errors trigger retries before transitioning the record to &lt;code&gt;extraction_failed&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Interrupted jobs are recovered on startup using heartbeat timestamps, preventing jobs from stalling indefinitely in the &lt;code&gt;extracting&lt;/code&gt; state.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Fault Isolation in Multi-Agent Clinical Reasoning
&lt;/h2&gt;

&lt;p&gt;When synthesizing insights from clinical records, Documedic runs specialized sub-agents to extract discrete medical entities, such as diagnoses, active medications, and visit summaries. &lt;/p&gt;

&lt;p&gt;Previously, a failure in one sub-agent caused the entire case execution to abort. The current pipeline executes agent tasks using an isolated settlement strategy:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;allSettled&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;diagnosisAgent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;documentText&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;medicationAgent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;documentText&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;encounterAgent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;documentText&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;]);&lt;/span&gt;

&lt;span class="k"&gt;for &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="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&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;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fulfilled&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;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;persistEntities&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;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&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;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Sub-agent failure: &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;reason&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="c1"&gt;// Log partial failure to audit trail without invalidating valid extractions&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;p&gt;By decoupling individual extraction routines, healthy agents continue populating valid chart data while failed components flag explicit audit entries for review, ensuring reliable clinical decision support under real-world runtime conditions.&lt;/p&gt;

</description>
      <category>cdss</category>
      <category>nestjs</category>
      <category>medicalai</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Client Management</title>
      <dc:creator>Subhendu Das</dc:creator>
      <pubDate>Sat, 15 Aug 2026 09:35:44 +0000</pubDate>
      <link>https://dev.to/sumaninster/client-management-3mgb</link>
      <guid>https://dev.to/sumaninster/client-management-3mgb</guid>
      <description>&lt;h2&gt;
  
  
  Introduction to AI-Driven Client Management
&lt;/h2&gt;

&lt;p&gt;As a small business owner in India, managing customer conversations across multiple channels can be overwhelming. This is where GoSumo, an AI-powered client management platform, comes in. With its ability to handle customer conversations across WhatsApp, Instagram, SMS, Web Chat, and Email through a single AI-driven interface, GoSumo aims to simplify client management for Indian small businesses.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem of Manual Client Management
&lt;/h2&gt;

&lt;p&gt;Manual client management can lead to missed messages, delayed responses, and a lack of personalization, ultimately affecting customer satisfaction and loyalty. Small businesses in India need a solution that can help them manage customer conversations efficiently and effectively.&lt;/p&gt;

&lt;h2&gt;
  
  
  How GoSumo Works
&lt;/h2&gt;

&lt;p&gt;GoSumo's AI-driven interface uses smart routing, automated responses, and multi-tenant support to manage customer conversations. The platform is built with NestJS, TypeScript, Next.js, PostgreSQL, Redis, BullMQ, Prisma, Qdrant, and OpenRouter AI, ensuring a robust and scalable architecture. With GoSumo, small businesses can handle customer inquiries, provide personalized responses, and resolve issues promptly, all from a single interface.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Benefits of AI-Driven Client Management
&lt;/h2&gt;

&lt;p&gt;By using GoSumo, small businesses in India can benefit from improved customer satisfaction, increased efficiency, and enhanced personalization. The platform's AI-driven interface ensures that customer conversations are managed promptly and effectively, reducing the risk of missed messages and delayed responses. Additionally, GoSumo's multi-channel support enables small businesses to engage with customers across multiple platforms, including WhatsApp business, Instagram, SMS, Web Chat, and Email.&lt;/p&gt;

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

&lt;p&gt;GoSumo's AI-powered client management platform is designed to help small businesses in India manage customer conversations efficiently and effectively. With its ability to handle multiple channels, provide personalized responses, and ensure prompt issue resolution, GoSumo is an ideal solution for Indian small businesses looking to improve their client management capabilities.&lt;/p&gt;

</description>
      <category>nestjs</category>
      <category>typescript</category>
      <category>nextjs</category>
      <category>postgres</category>
    </item>
    <item>
      <title>CDSS</title>
      <dc:creator>Subhendu Das</dc:creator>
      <pubDate>Sat, 15 Aug 2026 07:37:27 +0000</pubDate>
      <link>https://dev.to/sumaninster/cdss-533j</link>
      <guid>https://dev.to/sumaninster/cdss-533j</guid>
      <description>&lt;h2&gt;
  
  
  Introduction to Clinical Decision Support
&lt;/h2&gt;

&lt;p&gt;The Aether Clinician, also known as Documedic, is an AI clinical decision support system (CDSS) built with NestJS, React, PostgreSQL, and OpenRouter. It is designed for clinicians and physicians to provide medical AI-driven insights.&lt;/p&gt;

&lt;h2&gt;
  
  
  Problem
&lt;/h2&gt;

&lt;p&gt;One of the key challenges in healthcare is ensuring that clinicians have access to accurate and up-to-date information to make informed decisions. This is where the CDSS comes in, providing a system that can analyze patient data and provide recommendations based on the latest medical research and guidelines.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it Works
&lt;/h2&gt;

&lt;p&gt;The CDSS uses a combination of natural language processing (NLP) and machine learning algorithms to analyze patient data and provide recommendations. The system is built using a microservices architecture, with each service responsible for a specific task, such as data ingestion, processing, and visualization.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example Code
&lt;/h2&gt;

&lt;p&gt;The following code snippet shows an example of how the CDSS uses React to render patient data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight jsx"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&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;react&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;PatientData&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;./PatientData&lt;/span&gt;&lt;span class="dl"&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;PatientProfile&lt;/span&gt; &lt;span class="o"&gt;=&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="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;PatientData&lt;/span&gt; &lt;span class="na"&gt;patientId&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt; &lt;span class="p"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;div&lt;/span&gt;&lt;span class="p"&gt;&amp;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;
  
  
  Safety Features
&lt;/h2&gt;

&lt;p&gt;The CDSS also includes a number of safety features, such as automatic flagging of potential drug interactions and allergies. The system uses a rules-based approach to identify potential safety issues, and provides alerts and warnings to clinicians to ensure that they are aware of any potential risks.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recent Updates
&lt;/h2&gt;

&lt;p&gt;Recent updates to the CDSS have focused on improving the safety and accuracy of the system. For example, a recent commit fixed an issue where a prescription that stopped a drug was not being properly removed from the patient's chart. Another commit fixed an issue where a drug was being evaluated against an empty vocabulary, resulting in incorrect recommendations.&lt;/p&gt;

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

&lt;p&gt;The Aether Clinician CDSS is a powerful tool for clinicians and physicians, providing access to accurate and up-to-date medical information and insights. The system's use of AI and machine learning algorithms, combined with its focus on safety and accuracy, make it an essential tool for healthcare professionals.&lt;/p&gt;

</description>
      <category>nestjs</category>
      <category>react</category>
      <category>postgres</category>
    </item>
    <item>
      <title>Optimizing Panic-Value Ranking in Documedic’s CDSS</title>
      <dc:creator>Subhendu Das</dc:creator>
      <pubDate>Fri, 14 Aug 2026 04:37:23 +0000</pubDate>
      <link>https://dev.to/sumaninster/optimizing-panic-value-ranking-in-documedics-cdss-4h01</link>
      <guid>https://dev.to/sumaninster/optimizing-panic-value-ranking-in-documedics-cdss-4h01</guid>
      <description>&lt;h2&gt;
  
  
  Problem Statement
&lt;/h2&gt;

&lt;p&gt;Documedic is a CDSS that delivers real‑time clinical decision support to physicians.  In a typical workflow the clinician opens the &lt;em&gt;Panic‑Value&lt;/em&gt; pane to see the most abnormal laboratory results for a patient.  The original implementation fetched the entire patient chart, then performed the ranking in application memory.  For charts that contain hundreds of lab observations, this operation added 200 ms of latency and consumed a large amount of RAM on the API server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution Overview
&lt;/h2&gt;

&lt;p&gt;The new feature moves the ranking logic into PostgreSQL.  Using a window function, the query returns only the top N results, eliminating the need to stream the entire chart across the network and reducing the amount of data the NestJS service must process.  The change is a pure performance improvement; no new business logic or external data sources are introduced.&lt;/p&gt;

&lt;h2&gt;
  
  
  Technical Implementation
&lt;/h2&gt;

&lt;p&gt;The change is reflected in the &lt;code&gt;PanicValuesService&lt;/code&gt; inside &lt;code&gt;src/panic-values/panic-values.service.ts&lt;/code&gt;.&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;// src/panic-values/panic-values.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;PanicValuesService&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;prisma&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PrismaService&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;

  &lt;span class="cm"&gt;/**
   * Returns the top `limit` panic‑value records for a patient.
   * Ranking is performed in SQL using a window function.
   */&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;getTopPanicValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;patientId&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;limit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&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;rows&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;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$queryRaw&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;unknown&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="nx"&gt;Prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sql&lt;/span&gt;&lt;span class="s2"&gt;`
        SELECT
          lab_id,
          value,
          unit,
          reference_range,
          ROW_NUMBER() OVER (ORDER BY ABS(value - reference_value) DESC) AS rank
        FROM
          lab_results
        WHERE
          patient_id = &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;patientId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;
          AND is_abnormal = true
        ORDER BY
          rank
        LIMIT &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;limit&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;return&lt;/span&gt; &lt;span class="nx"&gt;rows&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;p&gt;The SQL uses &lt;code&gt;ROW_NUMBER()&lt;/code&gt; to rank results by the absolute deviation from the reference value.  The &lt;code&gt;LIMIT&lt;/code&gt; clause guarantees that only the requested number of rows is materialized.&lt;/p&gt;

&lt;p&gt;The controller simply forwards the request to the service:&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;// src/panic-values/panic-values.controller.ts&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;UseGuards&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;AuthGuard&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;panic-values&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;getPanicValues&lt;/span&gt;&lt;span class="p"&gt;(@&lt;/span&gt;&lt;span class="nd"&gt;Req&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;limit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;limit&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="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;patientId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;patientId&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;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;panicValuesService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getTopPanicValues&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;patientId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;10&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;
  
  
  Front‑End Integration
&lt;/h2&gt;

&lt;p&gt;On the React side, the component &lt;code&gt;PanicValueTable&lt;/code&gt; calls the API and renders the ranked list.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/components/PanicValueTable.tsx&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PanicValueTable&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FC&lt;/span&gt; &lt;span class="o"&gt;=&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setValues&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;LabResult&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="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;useEffect&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="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/panic-values?limit=10&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;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;res&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;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;setValues&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;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;table&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;thead&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;th&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Lab&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;th&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;th&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Value&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;th&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;th&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Unit&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;th&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;th&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Reference&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;th&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;th&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;Rank&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;th&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;thead&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;tbody&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;v&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="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt; &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lab_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;labgrade&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;unit&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reference_range&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
            &lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rank&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;td&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
          &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;tr&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
        &lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;tbody&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="p"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="nt"&gt;table&lt;/span&gt;&lt;span class="p"&gt;&amp;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;p&gt;The component remains unchanged in terms of UI; the only difference is that the data arriving from the server is now already sorted and trimmed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Impact and Observations
&lt;/h2&gt;

&lt;p&gt;The refactor reduces the API response size by up to 90 % for patients with large charts.  Because the ranking is performed in the database, the NestJS process no longer allocates memory for the entire chart, lowering the CPU load on the server.  In a recent load test the average latency for the panic‑value endpoint dropped from 240 ms to 60 ms when the patient chart contained 300 records.&lt;/p&gt;

&lt;p&gt;This performance gain directly benefits the clinician’s workflow: the &lt;em&gt;Panic‑Value&lt;/em&gt; pane populates almost instantly, allowing the physician to focus on decision‑making rather than waiting for data.  The change aligns with Documedic’s goal of delivering a responsive, reliable clinical decision support experience.&lt;/p&gt;




&lt;p&gt;Documedic is a NestJS/React/CDS‑based platform that integrates PostgreSQL for data storage and OpenRouter for LLM calls.  The new SQL‑based ranking showcases how a targeted database optimization can improve a medical AI system’s usability for clinicians.&lt;/p&gt;

</description>
      <category>nestjs</category>
      <category>react</category>
      <category>postgres</category>
      <category>api</category>
    </item>
    <item>
      <title>TalentPing: Feature Spotlight</title>
      <dc:creator>Subhendu Das</dc:creator>
      <pubDate>Fri, 07 Aug 2026 23:09:49 +0000</pubDate>
      <link>https://dev.to/sumaninster/talentping-feature-spotlight-34fj</link>
      <guid>https://dev.to/sumaninster/talentping-feature-spotlight-34fj</guid>
      <description>&lt;h2&gt;
  
  
  What TalentPing is
&lt;/h2&gt;

&lt;p&gt;AI recruiting autopilot with automated outreach, follow-ups, ATS boards, and weekly digests.&lt;/p&gt;

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

&lt;p&gt;Built with FastAPI, React, PostgreSQL, Celery.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed
&lt;/h2&gt;

&lt;p&gt;13 new commit(s) (showing the 10 most recent):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;fix(follow-ups): a nudge nobody approved counted as one the recruiter read&lt;/li&gt;
&lt;li&gt;fix(spam-risk): a portfolio and a LinkedIn counted as four links&lt;/li&gt;
&lt;li&gt;fix(bounce): the remote server's IP address was read as the status code&lt;/li&gt;
&lt;li&gt;fix(send-time): every west-coast recruiter was mailed on Eastern time&lt;/li&gt;
&lt;li&gt;chore(follow-ups): drop a registered task for a button that was never built&lt;/li&gt;
&lt;li&gt;fix(dashboard): every follow-up that went out was two events in the activity feed&lt;/li&gt;
&lt;li&gt;fix(follow-ups): the digest counted a nudge as sent while listing it as unapproved&lt;/li&gt;
&lt;li&gt;fix(follow-ups): a row that failed halfway still shipped the email it half-wrote&lt;/li&gt;
&lt;li&gt;fix(tracking): the subject experiment scored almost none of the opens it measured&lt;/li&gt;
&lt;li&gt;fix(limits): three more buttons that spend somebody else's quota&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;See it at &lt;a href="https://talentping.aiknol.com" rel="noopener noreferrer"&gt;https://talentping.aiknol.com&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Drafted by Herald from the project record: no AI provider was reachable at generation time. Rewrite before publishing.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>fastapi</category>
      <category>react</category>
      <category>postgres</category>
      <category>celery</category>
    </item>
    <item>
      <title>How AI is Changing Job Applications in 2026</title>
      <dc:creator>Subhendu Das</dc:creator>
      <pubDate>Fri, 07 Aug 2026 23:09:47 +0000</pubDate>
      <link>https://dev.to/sumaninster/how-ai-is-changing-job-applications-in-2026-e51</link>
      <guid>https://dev.to/sumaninster/how-ai-is-changing-job-applications-in-2026-e51</guid>
      <description>&lt;p&gt;The job hunt has an arithmetic problem. The cost of sending an application has fallen to nearly zero, so the number of applications per opening has gone up, so the odds on any single one have gone down, so candidates send more. Everyone is running faster to stay in the same place, and the tooling that arrived to help mostly just made the treadmill quicker.&lt;/p&gt;

&lt;p&gt;The interesting shift in 2026 is not that AI can write a cover letter. It is that AI can finally do the part that was never about writing: deciding &lt;strong&gt;which openings are real&lt;/strong&gt;, &lt;strong&gt;who is actually behind them&lt;/strong&gt;, and &lt;strong&gt;what to say to that specific person&lt;/strong&gt;. Those are the three places where a job search leaks time, and they are the three places TalentPing puts its effort.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first problem: a lot of jobs aren't jobs
&lt;/h2&gt;

&lt;p&gt;Roughly a fifth of scraped postings are ghosts — roles that expired, were never budgeted, or exist to farm a resume pipeline. They are invisible from the candidate's side. A ghost posting looks exactly like a real one until three weeks of silence go by, and by then the candidate has drawn the wrong conclusion. Not &lt;em&gt;"that role was never open"&lt;/em&gt; but &lt;em&gt;"I am not good enough"&lt;/em&gt;, or worse, &lt;em&gt;"this tool is broken"&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;TalentPing scores every posting for ghost risk before it ever reaches an outreach queue. The signals are read off data the fetchers already collect, so it costs no extra requests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Age.&lt;/strong&gt; A role advertised as open for 120 days is not being filled at the rate it claims.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repost count.&lt;/strong&gt; The same posting fingerprint resurfacing across scans with a &lt;em&gt;fresher&lt;/em&gt; date. One repost is ordinary hiring. Four is a requisition that never closes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evergreen language.&lt;/strong&gt; "General application", "open application", "join our talent community" — phrases with no innocent reading. A real posting says &lt;em&gt;we are hiring&lt;/em&gt;; that phrase is not a signal, and it is deliberately not on the list.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Breadth without a home.&lt;/strong&gt; A role on four aggregators and on no company board is much weaker evidence of an opening than the same role on the employer's own Greenhouse. The company board is the employer speaking directly, and it comes down when the role closes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two design decisions matter more than the signal list.&lt;/p&gt;

&lt;p&gt;The first is that this is &lt;strong&gt;a risk score, not a verdict&lt;/strong&gt;. Scoring is additive, every contribution carries the sentence that earned it, and those sentences go to the candidate. You do not get told "hidden — trust us". You get told &lt;em&gt;"open 96 days; reposted 3 times; not on the company's own board"&lt;/em&gt; and you decide.&lt;/p&gt;

&lt;p&gt;The second is that the default suppression bar sits deliberately high — a posting needs several independent signals to clear it. Age alone can never get there. The failure mode worth avoiding is a real job silently dropped, and screening loose is a much cheaper mistake than screening tight.&lt;/p&gt;

&lt;p&gt;The board signal is the only one that can push risk &lt;em&gt;down&lt;/em&gt;. A posting fetched from an employer's own applicant tracking system is live by construction, and that outweighs an inherited age or an unlucky phrase.&lt;/p&gt;

&lt;h2&gt;
  
  
  The second problem: the ATS is a wall, not a door
&lt;/h2&gt;

&lt;p&gt;The conventional advice is to apply through the company's portal. The conventional experience is that the portal is where applications go to be counted rather than read.&lt;/p&gt;

&lt;p&gt;TalentPing reads employer ATS boards directly — &lt;strong&gt;Greenhouse, Lever, Ashby, Workable and SmartRecruiters&lt;/strong&gt; — because that is the freshest and most honest source of what a company is actually hiring for. A role on the company's own board came from the company, is dated by the company, and disappears when it is filled. Everything downstream in the pipeline is better for having started there: the ghost scoring trusts it, the matching has real requirements to work with, and the outreach can name the actual role.&lt;/p&gt;

&lt;p&gt;But reading the board is not the same as applying through it. TalentPing uses the board as &lt;em&gt;intelligence&lt;/em&gt;, then routes around the form.&lt;/p&gt;

&lt;h2&gt;
  
  
  The third problem: the form was never the channel
&lt;/h2&gt;

&lt;p&gt;Instead of submitting into a queue, TalentPing finds a human.&lt;/p&gt;

&lt;p&gt;Resolving a company name to the right contact is less trivial than it sounds, and most of the work is verification rather than search. A responding domain proves nothing — &lt;code&gt;linear.io&lt;/code&gt; is parked while the company is at &lt;code&gt;linear.app&lt;/code&gt;. So the crawler resolves the domain, confirms the site genuinely belongs to that company, locates the careers page, and extracts addresses in confidence order: &lt;code&gt;mailto:&lt;/code&gt; links first, then bare addresses in the page text, then LinkedIn recruiter profiles. &lt;code&gt;noreply@&lt;/code&gt;, &lt;code&gt;press@&lt;/code&gt;, freemail and third-party vendor addresses are filtered out. When a company publishes nothing at all, it falls back to conventional role mailboxes — &lt;code&gt;careers@&lt;/code&gt;, &lt;code&gt;jobs@&lt;/code&gt; — explicitly marked at low confidence, so a real find is never dressed up as a guess.&lt;/p&gt;

&lt;p&gt;Then it writes the email. One per contact, grounded in the specific role and the specific resume, because the entire premise collapses if the output reads like a mail merge.&lt;/p&gt;

&lt;p&gt;And it sends from &lt;strong&gt;your own Gmail&lt;/strong&gt;, via one-click OAuth. This is not a convenience detail. Mail sent as you is SPF/DKIM/DMARC aligned, so it lands in an inbox instead of a spam folder. Replies come back to your inbox, where you would look for them anyway. There is no third-party sending domain to build a reputation for, and no "sent via" footer announcing that a robot wrote it. Sends trickle out over hours against a warm-up budget and a hard daily limit — which is also exactly why ghost screening earns its keep, since every wasted send costs a slot that a real opening could have used.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "personalized" has to mean now
&lt;/h2&gt;

&lt;p&gt;Every product in this category claims personalization. In 2026 the word is doing almost no work, because a model that has read the job description can produce something superficially tailored for free. The differentiator is what the personalization is &lt;em&gt;grounded in&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;TalentPing's grounding is your resume — parsed on upload, with name, location, skills, seniority, job history and target roles extracted automatically. Upload several, one per role you are targeting, and each becomes a &lt;strong&gt;profile&lt;/strong&gt; with its own roles, locations, salary floor and level. Every job found is scored against all of them, and the best-matching profile is the one whose resume and cover letter actually go out.&lt;/p&gt;

&lt;p&gt;That last detail is the point. A candidate pitching both "senior backend" and "engineering manager" is two different candidates on paper, and sending the wrong one is worse than sending nothing.&lt;/p&gt;

&lt;p&gt;After the send, a tracker shows every message and its status. Replies are classified by intent and a suitable response is drafted, so the part of the process that actually requires a human — deciding what to say to someone who said yes — is the part you spend your attention on.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shape of the change
&lt;/h2&gt;

&lt;p&gt;The 2020s version of AI job tooling optimized for throughput: more applications, faster, with less typing. It worked, in the narrow sense that the number went up. It also made the number meaningless.&lt;/p&gt;

&lt;p&gt;The 2026 version optimizes for the opposite thing. Fewer sends, aimed at openings that verifiably exist, addressed to people who can actually act on them, from an address that reaches an inbox. It is a worse story for a dashboard and a much better one for a candidate.&lt;/p&gt;

&lt;p&gt;TalentPing is open source at &lt;a href="https://github.com/r2st/TalentPing" rel="noopener noreferrer"&gt;github.com/r2st/TalentPing&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>career</category>
      <category>jobsearch</category>
      <category>automation</category>
    </item>
    <item>
      <title>GSTBot: Feature Spotlight</title>
      <dc:creator>Subhendu Das</dc:creator>
      <pubDate>Fri, 07 Aug 2026 23:09:16 +0000</pubDate>
      <link>https://dev.to/sumaninster/gstbot-feature-spotlight-26am</link>
      <guid>https://dev.to/sumaninster/gstbot-feature-spotlight-26am</guid>
      <description>&lt;h2&gt;
  
  
  What GSTBot is
&lt;/h2&gt;

&lt;p&gt;GSTBot is an AI-powered GST compliance assistant for Indian SMBs. It ingests invoices by photo, PDF or Excel, extracts vendor GSTIN, invoice number, amount, tax rate and HSN code, then auto-reconciles the purchase register against GSTR-2B — flagging amount mismatches, missing invoices, duplicates and defaulting suppliers. On top of reconciliation it calculates eligible Input Tax Credit, flags Rule 37/42/43 reversals, scores supplier filing reliability, and pre-fills GSTR-1 and GSTR-3B in GST-portal-compatible JSON/CSV. Built for the businesses priced out of ClearTax Pro and Taxilla.&lt;/p&gt;

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

&lt;p&gt;Built with FastAPI, React, Vite, PostgreSQL, Redis, Celery, OpenRouter, Tesseract.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed
&lt;/h2&gt;

&lt;p&gt;12 new commit(s) (showing the 10 most recent):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Refuse an invoice date no invoice could carry&lt;/li&gt;
&lt;li&gt;Read the dashboard's seven months in one scan, not seven&lt;/li&gt;
&lt;li&gt;Refuse a GSTR-2B addressed to somebody else's registration&lt;/li&gt;
&lt;li&gt;Let a deleted invoice's number be used again&lt;/li&gt;
&lt;li&gt;Check the codes a reviewer types, as the extractor already does&lt;/li&gt;
&lt;li&gt;Let the loser of a supplier race keep its invoice&lt;/li&gt;
&lt;li&gt;Look a period's suppliers up once, not once per supplier&lt;/li&gt;
&lt;li&gt;Refuse tax charged on a taxable value of zero, which nothing was checking&lt;/li&gt;
&lt;li&gt;Count only credit as a supplier's credit at risk, as everything else does&lt;/li&gt;
&lt;li&gt;Judge a payment against the invoice date the row will hold, not the one sent&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;See it at &lt;a href="https://gstbot.aiknol.com" rel="noopener noreferrer"&gt;https://gstbot.aiknol.com&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Drafted by Herald from the project record: no AI provider was reachable at generation time. Rewrite before publishing.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>fastapi</category>
      <category>react</category>
      <category>vite</category>
      <category>postgres</category>
    </item>
    <item>
      <title>Making 409A Valuations Faster and Cheaper with AI</title>
      <dc:creator>Subhendu Das</dc:creator>
      <pubDate>Fri, 07 Aug 2026 23:09:15 +0000</pubDate>
      <link>https://dev.to/sumaninster/making-409a-valuations-faster-and-cheaper-with-ai-421d</link>
      <guid>https://dev.to/sumaninster/making-409a-valuations-faster-and-cheaper-with-ai-421d</guid>
      <description>&lt;p&gt;A 409A valuation is a strange product. Every venture-backed company in the US needs one, needs it again every twelve months or after any material event, and needs it to survive scrutiny from an auditor and potentially the IRS. It is mandatory, repetitive, highly structured — and it has traditionally taken weeks and cost thousands of dollars.&lt;/p&gt;

&lt;p&gt;That combination is unusual. Most work that is &lt;em&gt;both&lt;/em&gt; highly structured and expensive gets automated early. Valuations did not, for a reason worth taking seriously: the output has to be &lt;strong&gt;defensible&lt;/strong&gt;. A number that is fast, cheap and indefensible is worth less than no number at all, because it creates tax exposure rather than resolving it.&lt;/p&gt;

&lt;p&gt;So the interesting question is not "can AI produce a valuation faster". It obviously can. The question is what has to be true for the fast one to hold up.&lt;/p&gt;

&lt;p&gt;N409 is a production rebuild of the 409.ai AI-assisted valuation platform, and it is built around that question.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the time actually goes
&lt;/h2&gt;

&lt;p&gt;Ask an analyst where a 409A engagement's hours go and very little of the answer is the valuation itself. The math — Black-Scholes OPM, income, market and asset approaches — is well-defined and, once the inputs are clean, fast.&lt;/p&gt;

&lt;p&gt;The hours go to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Getting the inputs out of the documents.&lt;/strong&gt; A cap table as a spreadsheet with three merged-cell headers. An income statement as a PDF export. Projections in whatever shape the founder's model happened to take. A pitch deck with the only statement of what the company actually does.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Normalizing them.&lt;/strong&gt; Different companies name the same line item four ways.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Selecting comparables.&lt;/strong&gt; Finding public companies that are genuinely comparable, and being able to say why.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Writing the narrative.&lt;/strong&gt; Prose explaining the method and the judgment calls, most of which is structurally identical between engagements.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Every one of those is a task where a language model is genuinely strong and a spreadsheet is genuinely weak. None of them is the arithmetic.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pipeline
&lt;/h2&gt;

&lt;p&gt;N409's flow follows that observation directly.&lt;/p&gt;

&lt;p&gt;Uploaded financials — &lt;strong&gt;cap tables, income statements, balance sheets, projections, decks&lt;/strong&gt; — go to an AI layer that extracts and normalizes the data and selects public comparables. An &lt;strong&gt;R-based quantitative engine&lt;/strong&gt; then computes the valuation: Black-Scholes OPM, plus income, market and asset approaches. The AI drafts the narrative. An analyst reviews it, overrides anything that needs overriding, and the deliverable report is drafted, edited, versioned and published as a PDF.&lt;/p&gt;

&lt;p&gt;The division of labour is the point. &lt;strong&gt;AI does extraction, normalization, comparable selection and prose. A deterministic engine does the math. A human signs.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is not a hedge. It reflects what each part is reliable at. You do not want a language model doing option pricing, because you cannot reproduce or audit a number it produced by inference. You do not want an analyst spending four hours re-keying a cap table, because that is not judgment, it is transcription. And you cannot remove the analyst, because the signature is what makes the document mean anything.&lt;/p&gt;

&lt;p&gt;Coverage runs beyond 409A alone: &lt;strong&gt;ASC 718 and ASC 820, gift and estate valuations, QSBS, and EMI/CSOP&lt;/strong&gt; for UK option schemes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "audit-ready" has to mean
&lt;/h2&gt;

&lt;p&gt;This is where the design gets opinionated, and where most "AI does X" products in regulated work quietly fall over.&lt;/p&gt;

&lt;p&gt;A valuation is defensible when someone can reconstruct it. Not approximately — exactly. Which inputs, which method, which assumptions, who changed what, and when. If any step of that chain is a model output nobody recorded, the whole document is weaker than a spreadsheet a human made by hand.&lt;/p&gt;

&lt;p&gt;So the audit trail is a first-class part of the schema rather than a logging afterthought:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Every analyst override preserves the original value.&lt;/strong&gt; The computed number does not disappear when a human disagrees with it. Both are on the record, across all the documented valuation fields.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every AI-produced value stores its provenance&lt;/strong&gt; — prompt version, model, anonymized inputs, outputs, and the human acceptance that let it through. "The AI said so" is not a citation. "Model X, prompt version Y, these inputs, accepted by this analyst on this date" is.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Every AI job is persisted&lt;/strong&gt; with status, model, cost and latency, so the pipeline is inspectable as a system, not just as a result.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An immutable trail of inputs, model and state changes&lt;/strong&gt;, with published reports retained, aligned to AICPA practice-aid expectations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is a privacy consequence that falls out of the same thinking. Cap tables and financials are among the most sensitive documents a company has, and they are exactly what this system ingests. &lt;strong&gt;Cap-table data is anonymized before it goes to any LLM&lt;/strong&gt;, PII is redacted from logs, access is least-privilege RBAC, and everything is encrypted in transit and at rest. Sending a client's raw cap table to a third-party API because it was convenient is not a trade a valuation firm can make.&lt;/p&gt;

&lt;h2&gt;
  
  
  Faster reports, and what makes them fast
&lt;/h2&gt;

&lt;p&gt;The report is the deliverable, and it is where "weeks" usually becomes "weeks".&lt;/p&gt;

&lt;p&gt;N409 treats it as a versioned artifact bound to a template version: a WYSIWYG editor over the drafted narrative, PDF rendering, and full version history. Because the narrative arrives pre-drafted from data the engine already computed, the analyst's job shifts from &lt;em&gt;writing a report&lt;/em&gt; to &lt;em&gt;reviewing one&lt;/em&gt; — and the recalculate path lets a changed input propagate through without starting over.&lt;/p&gt;

&lt;p&gt;The speed does not come from skipping steps. It comes from the steps that were always mechanical no longer being done by hand.&lt;/p&gt;

&lt;h2&gt;
  
  
  The partner channel, and why white-label matters here
&lt;/h2&gt;

&lt;p&gt;Most companies that need a 409A already have a relationship with someone — their accounting firm, their cap-table platform, their equity-management provider. That relationship is the natural distribution channel, and it is a much better one than trying to acquire startups directly.&lt;/p&gt;

&lt;p&gt;N409's architecture treats partners as a first-class channel rather than an afterthought: a &lt;strong&gt;partner API&lt;/strong&gt; with white-labelled report delivery, and per-partner branding so an accounting firm can offer valuations under its own name. The roadmap for that channel — custom domains, report logo and colour, sender identity, a co-branded client portal — is explicit and in progress rather than shipped, and worth stating plainly: today the branding foundations exist and the full white-label surface is being built out.&lt;/p&gt;

&lt;p&gt;The strategic logic is straightforward. A firm that already does a client's books is better placed to collect their financials than any vendor, and would rather deliver the valuation as its own work product than refer the client elsewhere. Making that possible is a platform decision, not a marketing one — it has to be in the tenancy model and the report pipeline from the start.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where this is
&lt;/h2&gt;

&lt;p&gt;N409 is a rebuild, and it is honest about its stage: discovery and full technical documentation are complete, and implementation is underway with the foundations in place. It is a TypeScript monorepo — valuation, web, AI, engine-wrapper and report services over PostgreSQL and Redis, with OpenTelemetry throughout and Terraform-managed infrastructure.&lt;/p&gt;

&lt;p&gt;The bet underneath it is simple. Valuation work is not expensive because the math is hard. It is expensive because the inputs arrive as PDFs and the output has to withstand an audit. AI is very good at the first problem and completely unhelpful with the second — so build a system that uses it for the first and engineers hard for the second.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://n409.aiknol.com?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=n409&amp;amp;utm_content=making-409a-valuations-faster-and-cheaper-with-ai" rel="noopener noreferrer"&gt;n409.aiknol.com&lt;/a&gt; · &lt;a href="https://github.com/r2st/N409" rel="noopener noreferrer"&gt;github.com/r2st/N409&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>fintech</category>
      <category>valuation</category>
      <category>startup</category>
    </item>
    <item>
      <title>Why Every Developer Needs an AI Assistant on Their Messaging Platform</title>
      <dc:creator>Subhendu Das</dc:creator>
      <pubDate>Tue, 04 Aug 2026 00:25:15 +0000</pubDate>
      <link>https://dev.to/sumaninster/why-every-developer-needs-an-ai-assistant-on-their-messaging-platform-2nk2</link>
      <guid>https://dev.to/sumaninster/why-every-developer-needs-an-ai-assistant-on-their-messaging-platform-2nk2</guid>
      <description>&lt;p&gt;Your AI assistant lives in a browser tab. Your actual life happens in a messaging app.&lt;/p&gt;

&lt;p&gt;That gap is small enough to ignore most days and expensive on the days that matter. The idea arrives on a walk, the production question arrives at dinner, the "what was that flag called again" arrives in the ten seconds before your laptop wakes up. By the time you are at a keyboard with the right tab open, the thought has either been written down badly or lost.&lt;/p&gt;

&lt;p&gt;Telechat closes the gap by putting Claude where you already are: &lt;strong&gt;Telegram, WhatsApp, Slack, Discord, and a local web chat — all at once, from a single Python process on your own machine.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  One process, five surfaces, one memory
&lt;/h2&gt;

&lt;p&gt;The multi-platform part is not a checkbox. It is the thing that makes the rest work.&lt;/p&gt;

&lt;p&gt;Most bots are one bot per platform, which means one conversation history per platform, which means the context you built up on your phone is not there when you switch to the desktop. Telechat runs every adapter in one process against one SQLite file, so sessions, memories and history are genuinely shared. You can start a thought on Telegram at a bus stop, keep going in the local web chat when you sit down, and it is the same conversation — not a second bot that has never met you.&lt;/p&gt;

&lt;p&gt;Set &lt;code&gt;BOT_MODE=all&lt;/code&gt; and every surface comes up together. Set it to a comma-separated subset and only those do.&lt;/p&gt;

&lt;p&gt;There is no webhook, no tunnel, no public URL, and no Telechat server anywhere in the path. Your conversations go from your messenger to your machine to Anthropic, and nowhere else. For a tool you will end up telling quite a lot about your work, "self-hosted" is a meaningfully different security posture from "we promise not to look".&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting to a working bot
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; telechat
telechat init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;telechat init&lt;/code&gt; is an interactive setup that uses the Claude CLI to walk you through each platform — it opens the right pages, grabs your tokens, validates them, and writes your config. There is also &lt;code&gt;telechat setup&lt;/code&gt; for a plain step-by-step wizard with no Claude CLI required.&lt;/p&gt;

&lt;p&gt;If you just want to see whether any of this is worth it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;telechat web
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That starts the local web chat on &lt;code&gt;127.0.0.1:8585&lt;/code&gt;. No bot token, no messenger account, nothing written to disk. It needs only a way to reach Claude — the Claude Code CLI, or an &lt;code&gt;ANTHROPIC_API_KEY&lt;/code&gt; — and it tells you which one is missing if neither is there.&lt;/p&gt;

&lt;p&gt;Two ways to reach Claude, and the choice matters for cost:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;CLI mode&lt;/strong&gt; uses the Claude Code CLI. If you already pay for a Claude subscription, there is no API key and no per-token bill.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;API mode&lt;/strong&gt; uses the Anthropic API directly. Needs a key, and works in Docker.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Group AI, without the group hating it
&lt;/h2&gt;

&lt;p&gt;The failure mode of a bot in a shared room is that it will not stop talking. Every bot that has ever been added to a busy channel and removed a day later was removed for this reason.&lt;/p&gt;

&lt;p&gt;Telechat's group behaviour defaults to restraint: it answers when mentioned or replied to, and stays quiet otherwise. &lt;code&gt;/groupmode mention|all|off&lt;/code&gt; changes that per room, so a channel that genuinely wants an always-on participant can have one and everywhere else stays calm.&lt;/p&gt;

&lt;p&gt;Each room gets &lt;strong&gt;its own conversation&lt;/strong&gt;. The bot in your team's incident channel does not carry over context from your private chat, which is both the correct privacy behaviour and the correct usefulness behaviour — a group thread should read like a group thread, not like it is halfway through someone else's discussion.&lt;/p&gt;

&lt;p&gt;Access is handled the same way, socially rather than administratively. &lt;code&gt;/invite&lt;/code&gt; mints a one-tap link that grants a friend access — no user IDs to collect, no &lt;code&gt;.env&lt;/code&gt; edit, no restart — and &lt;code&gt;/invites&lt;/code&gt; shows who joined through yours.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tools, MCP, and the part that stops it being a chat toy
&lt;/h2&gt;

&lt;p&gt;An assistant you can only talk to is a novelty. The value shows up when it can &lt;em&gt;do&lt;/em&gt; things, and Telechat ships a working set of tools behind slash commands:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;/code&lt;/code&gt;, &lt;code&gt;/project&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Start a coding task; set the working directory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/plan&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Two-agent multi-step planning with progress updates&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;/search&lt;/code&gt;, &lt;code&gt;/fetch&lt;/code&gt;, &lt;code&gt;/web&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Web search, URL summarize, page browse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;/kb&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Knowledge base — upload documents, full-text and semantic search&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;/imagine&lt;/code&gt;, &lt;code&gt;/tts&lt;/code&gt;, &lt;code&gt;/music&lt;/code&gt;, &lt;code&gt;/video&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Image, speech, music and video generation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;/schedule&lt;/code&gt;, &lt;code&gt;/poll&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Scheduled tasks; polls&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Alongside these it speaks &lt;strong&gt;MCP&lt;/strong&gt;, so any Model Context Protocol server you already run — your database, your issue tracker, your internal API — becomes something you can reach from your phone. This is the piece that turns "Claude, but on Telegram" into an actual remote control for your own systems.&lt;/p&gt;

&lt;p&gt;Two features round it out. &lt;strong&gt;Memory&lt;/strong&gt; is per-user with full-text search, recalled into every reply, so context carries across sessions rather than dying with the chat window; &lt;code&gt;/remember&lt;/code&gt;, &lt;code&gt;/recall&lt;/code&gt; and &lt;code&gt;/forget&lt;/code&gt; let you manage it directly. And &lt;strong&gt;smart model routing&lt;/strong&gt; picks Haiku, Sonnet or Opus by query complexity, so a one-line question does not get billed at deep-reasoning rates.&lt;/p&gt;

&lt;p&gt;Voice messages work too: send a voice note and it answers the transcript, free via Groq's Whisper tier, no paid key required.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bridge, which is the part nobody else has
&lt;/h2&gt;

&lt;p&gt;If you use Claude Code, this is the feature worth the install on its own.&lt;/p&gt;

&lt;p&gt;When a Claude Code session on your machine finishes a turn or stalls on a question, &lt;strong&gt;you get a triage card on Telegram&lt;/strong&gt; — status first, the pending decision pulled to the top. Reply to the card and your answer is injected back into that session via &lt;code&gt;claude --resume&lt;/code&gt;. Pick from a session list if several are running.&lt;/p&gt;

&lt;p&gt;Turn on &lt;code&gt;--approval&lt;/code&gt; and every Bash, Write and Edit call waits for your tap, with the call preview and Approve / Deny / Always-allow buttons on the card. Standing rules resolve the repetitive ones without asking.&lt;/p&gt;

&lt;p&gt;Which means a long-running agent stops being something you have to babysit at a desk. It runs, it pages you when it genuinely needs a human, you answer from wherever you are, and it continues.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who this is actually for
&lt;/h2&gt;

&lt;p&gt;Telechat is explicit about its boundary: it is a &lt;strong&gt;single-operator tool&lt;/strong&gt;, and in CLI mode the bot runs with your local permissions. That is the right trade for a personal assistant on your own hardware, and the wrong one for a shared corporate deployment. Knowing which of those you are building matters more than any feature on the list.&lt;/p&gt;

&lt;p&gt;If you already use Claude and you have caught yourself wishing it were on your phone without handing your history to a third-party wrapper, that is the whole pitch.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; telechat &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; telechat init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://telechat.fyi?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=telechat&amp;amp;utm_content=why-every-developer-needs-an-ai-assistant-on-their-messaging-platform" rel="noopener noreferrer"&gt;telechat.fyi&lt;/a&gt; · &lt;a href="https://github.com/telechatai/telechat" rel="noopener noreferrer"&gt;github.com/telechatai/telechat&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>selfhosted</category>
      <category>python</category>
    </item>
    <item>
      <title>A self-hosted Claude bot whose chat history is a SQLite file I can delete</title>
      <dc:creator>Subhendu Das</dc:creator>
      <pubDate>Tue, 04 Aug 2026 00:25:14 +0000</pubDate>
      <link>https://dev.to/sumaninster/a-self-hosted-claude-bot-whose-chat-history-is-a-sqlite-file-i-can-delete-1gog</link>
      <guid>https://dev.to/sumaninster/a-self-hosted-claude-bot-whose-chat-history-is-a-sqlite-file-i-can-delete-1gog</guid>
      <description>&lt;p&gt;Every "chat with an AI from your phone" product asks you for the same thing: an account. Then your conversations live in a database you don't administer, subject to a retention policy you didn't write and can't audit.&lt;/p&gt;

&lt;p&gt;I wanted the opposite trade — Claude in my messengers, with the transcript on my own disk. That's &lt;a href="https://telechat.fyi?utm_source=devto&amp;amp;utm_medium=syndication&amp;amp;utm_campaign=telechat&amp;amp;utm_content=a-self-hosted-claude-bot-whose-chat-history-is-a-sqlite-file-i-can-delete" rel="noopener noreferrer"&gt;Telechat&lt;/a&gt;: a self-hosted Claude bot that runs as one Python process, speaks Telegram, WhatsApp, Slack and a local web chat at the same time, and stores everything in a single SQLite file.&lt;/p&gt;

&lt;p&gt;Here's what that actually buys you, and — because "privacy-first" is a claim people make without qualifying it — where the trust boundary really sits.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thirty seconds, no account anywhere
&lt;/h2&gt;

&lt;p&gt;The fastest way to see it work skips setup entirely:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx telechat web
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That starts a chat UI on &lt;code&gt;http://127.0.0.1:8585&lt;/code&gt;. No bot token. No messenger account. Nothing written to disk. It needs exactly one thing — a way to reach Claude, either the Claude Code CLI you already have or an &lt;code&gt;ANTHROPIC_API_KEY&lt;/code&gt; — and if neither is there it tells you which one is missing instead of failing obscurely.&lt;/p&gt;

&lt;p&gt;It binds to loopback. Not &lt;code&gt;0.0.0.0&lt;/code&gt; with a note in the README telling you to firewall it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The self-hosted part is the whole point
&lt;/h2&gt;

&lt;p&gt;A self-hosted Claude bot runs on your machine under your account. In CLI mode Telechat shells out to the &lt;code&gt;claude&lt;/code&gt; binary you already authenticated, so if you have a Claude subscription there's no API key and no second bill. In API mode it talks to Anthropic directly with your key.&lt;/p&gt;

&lt;p&gt;Either way there is no Telechat server in the path. I don't operate one. There's nothing to log into, so there's nothing of yours for me to hold. Your turn history, saved memories, knowledge base, session state and cost tracking all land in &lt;code&gt;~/.telechat/bot.db&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sqlite3 ~/.telechat/bot.db .tables   &lt;span class="c"&gt;# it's just a file&lt;/span&gt;
telechat &lt;span class="nb"&gt;env &lt;/span&gt;clean                   &lt;span class="c"&gt;# and credentials are just a file too&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can read it, grep it, back it up, or &lt;code&gt;rm&lt;/code&gt; it. That's the entire data-deletion story.&lt;/p&gt;

&lt;h2&gt;
  
  
  One process, four messengers
&lt;/h2&gt;

&lt;p&gt;Which platforms start is one environment variable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;BOT_MODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;telegram          &lt;span class="c"&gt;# the default&lt;/span&gt;
&lt;span class="nv"&gt;BOT_MODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;telegram,slack    &lt;span class="c"&gt;# a comma-separated list&lt;/span&gt;
&lt;span class="nv"&gt;BOT_MODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;all               &lt;span class="c"&gt;# all four&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each adapter owns its platform's quirks — Telegram's inline keyboards, Slack's thread semantics, WhatsApp's total lack of interactivity — then hands a plain string to the same invocation layer.&lt;/p&gt;

&lt;p&gt;The payoff isn't that it saves RAM. It's that they share the store, so they share your conversation. A memory you saved from Telegram is loaded when you ask from Slack. A session you started at your desk in the web chat is on the list when you pick it up on your phone. That's only true because there is exactly one process and exactly one database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Nothing on the internet has to reach you
&lt;/h2&gt;

&lt;p&gt;This is the design decision that removes most of the attack surface, and it's easy to miss because it shows up as an absence.&lt;/p&gt;

&lt;p&gt;The usual chat-bot tutorial reaches for a webhook. Accept an inbound HTTP request and you now need a public URL, a TLS certificate, and a reply inside the platform's timeout — which an LLM turn routinely blows past, so you need a queue, so you need somewhere to run the queue.&lt;/p&gt;

&lt;p&gt;Telechat never accepts an inbound connection from a platform:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;Transport&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Telegram&lt;/td&gt;
&lt;td&gt;Long polling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WhatsApp&lt;/td&gt;
&lt;td&gt;Green API free tier, polled over HTTPS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Slack&lt;/td&gt;
&lt;td&gt;Socket Mode — an outbound WebSocket the app opens&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Web chat&lt;/td&gt;
&lt;td&gt;Local aiohttp server on &lt;code&gt;127.0.0.1&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All four are outbound. No inbound firewall rule, no tunnel, no certificate to renew, nothing exposed. It works behind NAT, on hotel Wi-Fi, on a corporate network, and on a laptop that closes — when it wakes, polling resumes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The part "privacy-first" posts usually leave out
&lt;/h2&gt;

&lt;p&gt;Self-hosted is not automatically safe, and the honest version matters more than the marketing one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;In CLI mode, this bot is a remote shell.&lt;/strong&gt; It runs &lt;code&gt;claude&lt;/code&gt; with your authentication and your filesystem access. Anyone who can message the bot can ask Claude to read, write, or execute, bounded only by &lt;code&gt;CLAUDE_CLI_PERMISSION_MODE&lt;/code&gt; and &lt;code&gt;CLAUDE_CLI_WORK_DIR&lt;/code&gt;. So:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Always set an allowlist.&lt;/strong&gt; &lt;code&gt;TELEGRAM_ALLOWED_USER_IDS&lt;/code&gt;, &lt;code&gt;WHATSAPP_ALLOWED_NUMBERS&lt;/code&gt;, &lt;code&gt;SLACK_ALLOWED_USER_IDS&lt;/code&gt;. Empty means anyone who finds your bot can use it. The setup wizard warns about this and the warning is not decorative.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The allowlist is flat.&lt;/strong&gt; Conversations and memory are keyed per user; capability is not. Everyone on that list shares one Claude auth, one working directory, and one permission ceiling. Adding a second person grants them what it grants you. This is a personal single-operator tool, not a multi-tenant deployment, and per-user isolation does not exist yet.&lt;/p&gt;

&lt;p&gt;There's also a class of bug worth naming because it's specific to running on a machine you care about. Telechat reads links you paste, so a pasted URL is an outbound request from &lt;em&gt;inside&lt;/em&gt; your network. A recent fix closed the obvious holes: redirects are now followed one hop at a time with every hop re-checked, and hostnames are resolved with every resolved address checked — so a public link answering &lt;code&gt;302 Location: http://169.254.169.254/&lt;/code&gt; no longer reaches cloud metadata, and a hostname pointing at &lt;code&gt;127.0.0.1&lt;/code&gt; no longer reaches your own services. DNS rebinding is still open; closing it means pinning connections to a vetted address, which is a larger change. That's documented rather than glossed.&lt;/p&gt;

&lt;p&gt;If that trade-off doesn't suit you, it should be visible &lt;em&gt;before&lt;/em&gt; you install, not after.&lt;/p&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; telechat
telechat init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;telechat init&lt;/code&gt; walks each platform interactively, opens the right pages, grabs your tokens, validates them, and writes your config. There's &lt;code&gt;pip install telechatai&lt;/code&gt; if you'd rather, and &lt;code&gt;telechat doctor&lt;/code&gt; when something's off.&lt;/p&gt;

&lt;p&gt;MIT licensed, source at &lt;a href="https://github.com/telechatai/telechat" rel="noopener noreferrer"&gt;github.com/telechatai/telechat&lt;/a&gt;. It's a small project and the &lt;a href="https://github.com/telechatai/telechat/blob/main/SECURITY.md" rel="noopener noreferrer"&gt;security policy&lt;/a&gt; describes the trust boundary in more detail than most tools this size bother with — which is, I think, the least you should expect from anything you're about to hand your filesystem to.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>selfhosted</category>
      <category>privacy</category>
    </item>
    <item>
      <title>Telechat: Feature Spotlight</title>
      <dc:creator>Subhendu Das</dc:creator>
      <pubDate>Tue, 04 Aug 2026 00:24:42 +0000</pubDate>
      <link>https://dev.to/sumaninster/telechat-feature-spotlight-4aml</link>
      <guid>https://dev.to/sumaninster/telechat-feature-spotlight-4aml</guid>
      <description>&lt;h2&gt;
  
  
  What Telechat is
&lt;/h2&gt;

&lt;p&gt;Telechat is a self-hosted Claude AI bot that runs on Telegram, WhatsApp, Slack, and web chat from a single process. It features a unique Claude Desktop Bridge for remote-controlling local AI sessions from your phone, smart model routing, persistent memory, coding agents, media generation, and cost tracking. Install via npm, pip, or Docker.&lt;/p&gt;

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

&lt;p&gt;Built with Python, Claude AI, Telegram Bot API, WhatsApp, Slack, SQLite, Docker.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed
&lt;/h2&gt;

&lt;p&gt;13 new commit(s) (showing the 10 most recent):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;feat(web-chat): dark mode, social sharing meta tags, and a11y pass&lt;/li&gt;
&lt;li&gt;test(bridge): cover the commands and callbacks users actually touch&lt;/li&gt;
&lt;li&gt;fix: escape FTS queries in the KB, and create per-chat locks atomically&lt;/li&gt;
&lt;li&gt;fix: two bounded-collection bugs found reading the adapters&lt;/li&gt;
&lt;li&gt;feat(health): report the write path, which nothing could read before&lt;/li&gt;
&lt;li&gt;docs: rewrite the architecture doc to describe the program that exists&lt;/li&gt;
&lt;li&gt;fix: stop swallowing failures without a trace&lt;/li&gt;
&lt;li&gt;docs: add the repository conventions a contributor looks for&lt;/li&gt;
&lt;li&gt;docs: generate a complete configuration reference, and fix two dead settings&lt;/li&gt;
&lt;li&gt;feat(bridge): make the approval timeout a policy, not an accident&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;See it at &lt;a href="https://telechat.fyi" rel="noopener noreferrer"&gt;https://telechat.fyi&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Drafted by Herald from the project record: no AI provider was reachable at generation time. Rewrite before publishing.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>claudeai</category>
      <category>telegrambotapi</category>
      <category>whatsapp</category>
    </item>
  </channel>
</rss>
