<?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: Krish Verma</title>
    <description>The latest articles on DEV Community by Krish Verma (@krish_verma_77e28d3fd63ca).</description>
    <link>https://dev.to/krish_verma_77e28d3fd63ca</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%2F3710608%2F08842ad2-8dfd-4b6a-ae46-060bdd4a055d.png</url>
      <title>DEV Community: Krish Verma</title>
      <link>https://dev.to/krish_verma_77e28d3fd63ca</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/krish_verma_77e28d3fd63ca"/>
    <language>en</language>
    <item>
      <title>How I built deferred tool discovery for my desktop AI assistant (no embeddings needed)</title>
      <dc:creator>Krish Verma</dc:creator>
      <pubDate>Sat, 26 Sep 2026 10:26:04 +0000</pubDate>
      <link>https://dev.to/krish_verma_77e28d3fd63ca/how-i-built-deferred-tool-discovery-for-my-desktop-ai-assistant-no-embeddings-needed-1f57</link>
      <guid>https://dev.to/krish_verma_77e28d3fd63ca/how-i-built-deferred-tool-discovery-for-my-desktop-ai-assistant-no-embeddings-needed-1f57</guid>
      <description>&lt;p&gt;I'm building &lt;a href="https://github.com/akyourowngames/A.N.K.I.T.A" rel="noopener noreferrer"&gt;Ankita&lt;/a&gt;, an open-source desktop AI assistant (Electron + terminal CLI) that can run shell commands, edit files with approval diffs, search the live web, manage scheduled routines, and talk hands-free. It's powered by GitHub Copilot models, and one design constraint has shaped almost everything in its architecture:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The CLI has zero runtime npm dependencies.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That constraint is really about context, not packaging. When your assistant calls &lt;code&gt;web_search&lt;/code&gt; or &lt;code&gt;git_diff&lt;/code&gt;, the model needs the full parameter schema for every tool it might pick — and those schemas are context you pay for on every single request. Ship dozens of tools with everything loaded upfront and you burn tokens before the user has typed anything.&lt;/p&gt;

&lt;p&gt;So I built a deferred tool-discovery system. Here's how it works, and the three small decisions that made it feel invisible.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem: schemas are context
&lt;/h2&gt;

&lt;p&gt;Ankita's tool catalogue covers web search/fetch/scrape, Git, filesystem, process management, scheduling, page watches, GitHub notifications, project memory, MCP servers, image generation, voice… Each tool is one ESM module under &lt;code&gt;tools/&lt;/code&gt;, exporting &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;parameters&lt;/code&gt;, and &lt;code&gt;run()&lt;/code&gt;. If I loaded all of them into every request, the model would see hundreds of lines of JSON schema before the conversation even starts.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision 1: one &lt;code&gt;find_tools&lt;/code&gt; tool, not fifty
&lt;/h2&gt;

&lt;p&gt;The assistant's default toolset is deliberately small. When it needs something outside that set, it calls a single tool:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;find_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;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Load extra tools that are not in your default set: searching the internet and scraping pages, &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="s2"&gt;Git, port/process management, scheduled routines and page watches, project management and memory, GitHub notifications, and &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="s2"&gt;directory creation. Call this first whenever a task needs one of those; the tools become callable &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="s2"&gt;immediately afterwards.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It takes a plain-language &lt;code&gt;query&lt;/code&gt; — "search the web", "remind me daily", "where does this project stand" — and returns the matched tool schemas. Those tools then become callable in the same session. The rest stay unloaded.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision 2: categories with keyword summaries, no embeddings
&lt;/h2&gt;

&lt;p&gt;The matching lives in &lt;code&gt;tools/catalog.mjs&lt;/code&gt;. Tools are grouped into categories, and each category carries a short summary plus a keyword list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;process&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;find port owners and terminate an approved process or port listener&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;keywords&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;port&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;process&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;pid&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;address in use&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;eaddrinuse&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;kill&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;listener&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;taskkill&lt;/span&gt;&lt;span class="dl"&gt;'&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="nx"&gt;portStatus&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;killProcess&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;&lt;code&gt;matchCategories()&lt;/code&gt; in &lt;code&gt;tools/find-tools.mjs&lt;/code&gt; then matches the user's query against category ids, keywords, and even tool names — with word-boundary regexes, not naive substring checks (so "port" doesn't fire on "transport"). It's pure and synchronous, which makes it trivially testable with Node's built-in test runner.&lt;/p&gt;

&lt;p&gt;No embeddings, no vector index, no extra dependency. This is a deliberate trade-off: embeddings would handle paraphrase better, but a curated keyword list is predictable, debuggable, and costs nothing at runtime. For a desktop app where I control both ends, predictability wins.&lt;/p&gt;

&lt;p&gt;There are also pragmatic disambiguation rules baked in — e.g. a "github notifications" query loads the built-in GitHub inbox category and &lt;em&gt;not&lt;/em&gt; the connectors category, so one request doesn't pull in an unrelated schema. Real usage is full of these collisions, and a comment in the code documenting &lt;em&gt;why&lt;/em&gt; beats cleverness every time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Decision 3: skills are just markdown, loaded on demand
&lt;/h2&gt;

&lt;p&gt;Tools cover &lt;em&gt;capabilities&lt;/em&gt;; skills cover &lt;em&gt;procedures&lt;/em&gt;. A skill is a &lt;code&gt;SKILL.md&lt;/code&gt; file with frontmatter (&lt;code&gt;name&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;suggested-tools&lt;/code&gt;) and markdown instructions below. The &lt;code&gt;skill&lt;/code&gt; tool loads one by name and returns the body, capped at 8000 characters:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt; &lt;span class="o"&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;requested&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;renderSkill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;found&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// "# Skill: commit-review\n...---\n" + body, capped&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Suggested tools are hints only — the skill never forces a tool call. This keeps procedural knowledge (like the repo's own conventions in &lt;code&gt;ankita-dev&lt;/code&gt;: read before editing, zero runtime deps, run tests before reporting) out of the system prompt and loaded only when the task actually matches the skill's description.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd do differently next
&lt;/h2&gt;

&lt;p&gt;The keyword lists are hand-maintained, and they drift: every time I add a tool I have to think about which synonyms users will type. I've considered generating candidate keywords at build time from tool descriptions and reviewing the diff — human curation with machine assistance, rather than either extreme. And the always-on categories (personal memory, skills) deserve a periodic review, because "always loaded" is a cost I should keep auditing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it and tell me what breaks
&lt;/h2&gt;

&lt;p&gt;Ankita is open source — &lt;a href="https://github.com/akyourowngames/A.N.K.I.T.A" rel="noopener noreferrer"&gt;akyourowngames/A.N.K.I.T.A&lt;/a&gt; — with portable Windows builds in every release. The code discussed here is in &lt;code&gt;tools/catalog.mjs&lt;/code&gt;, &lt;code&gt;tools/find-tools.mjs&lt;/code&gt;, and &lt;code&gt;tools/skills/skill.mjs&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you've built lazy tool-loading for an agent of your own — embeddings, keyword maps, something weirder — I'd genuinely like to hear what worked and what bit you. I'm 16, building this in public, and the feedback loop is the best part.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>ai</category>
      <category>showdev</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Looking for contributors for an AI learning platform (open source)</title>
      <dc:creator>Krish Verma</dc:creator>
      <pubDate>Sat, 28 Mar 2026 15:21:42 +0000</pubDate>
      <link>https://dev.to/krish_verma_77e28d3fd63ca/looking-for-contributors-for-an-ai-learning-platform-open-source-c89</link>
      <guid>https://dev.to/krish_verma_77e28d3fd63ca/looking-for-contributors-for-an-ai-learning-platform-open-source-c89</guid>
      <description>&lt;p&gt;🚀 &lt;strong&gt;Looking for Builders – Join Yantra AI&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We’re building &lt;strong&gt;Yantra&lt;/strong&gt; — an AI-powered learning system that teaches students like a real teacher (interactive labs, AI guidance, real skill-building) &lt;/p&gt;

&lt;p&gt;We need:&lt;br&gt;
• Code maintainers&lt;br&gt;
• Code reviewers&lt;br&gt;
• Testers&lt;br&gt;
• Frontend devs&lt;br&gt;
• Backend (Supabase) devs&lt;br&gt;
• AI/ML engineers&lt;/p&gt;

&lt;p&gt;⚠️ This is a &lt;strong&gt;volunteer project&lt;/strong&gt; (no pay) — but we’re aiming to build something &lt;em&gt;big&lt;/em&gt; in education.&lt;/p&gt;

&lt;p&gt;If you want to build something meaningful → DM / join 🚀&lt;br&gt;
Discord = krish_77847&lt;br&gt;
instagram = krishverma_vibe&lt;/p&gt;

</description>
      <category>ai</category>
      <category>learning</category>
      <category>opensource</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
