Priyanshu Yadav

● open to roles · IST

AI Engineer · New Delhi, India · 3+ years shipping

LLM systems that keep working after the demo.

RAG that answers from your documents with citations. Agents that call real tools inside real permissions. Pipelines that survive a dead model endpoint. On the side, Windows utilities that strangers download and improve.

Ask my work ↓Read the notesResume

Priyanshu Yadav sitting at a table in a glass conservatory full of plants, looking to the side.
Priyanshu Yadav · 2026
12 ★ · 6 forksClaude Profile Switcher2 external pull requests
93downloads of 3 Windows toolsGitHub Releases API · 2026-10-06
219,004companies harvested by the lead agentStartup India registry · internal run
75 minbug report → fixed releaseBrightness Tray issue #1

01 · Live on this page

Ask my work. A retrieval pipeline runs in your browser.

Your question is embedded with a small BGE model running in WebAssembly, matched against pre-embedded chunks of this site’s case studies, notes and code (dense search fused with BM25), and answered with citations. Every step is timed on the right. Nothing leaves your machine.

Try:

Press Ask. The first question loads a 34 MB embedding model into your browser (cached afterwards), then every question runs locally.

    trace—
    modelwaiting for a question
    tokenize
    embed
    dense
    bm25
    fuse
    compose

    Index built 2026-10-06: 87 chunks · 384-d · int8 quantised (44 KB). Model: bge-small-en-v1.5, q8, via transformers.js, fused with BM25 by reciprocal rank. Answers are extractive, so they can only quote what the site already says. The trace mirrors what I run in Langfuse at work.

    02 · Work

    Two systems, drawn. Everything else, in the ledger.

    The two I’d talk about first in an interview. One is my employer’s and stays private, so it is drawn rather than linked. The other began as a take-home and became an internal tool.

    Employees ask HR questions in the HRMS chat. The model answers from the company’s policies with citations, or proposes a tool call. A permission layer decides whether this user may run it, and every result is schema-checked before it comes back.

    An employee's question goes to an LLM router. One path retrieves policy passages and cites them. The other path proposes a tool call, which passes a permission check before an HRMS tool runs. Both paths end at a schema check before the reply. A denied tool call returns an explanation and makes no call. every hop traced · Langfuse · tokens · cost · latencyEmployeeHRMS chatRouterLLM · served by vLLManswer | tool_callRetrievepolicy index · top-k · citePermission checkrole → allowed toolsHRMS toolsleave · attendance · ticketdenied → explain, no callSchema checkvalidate → reply

    RazorInfoTech · 2026 · Python · LangGraph · vLLM · Langfuse · case study →

    Internal tool · began as a take-home

    Lead research pipeline with a human at the end

    A LangGraph supervisor runs research, qualification and sales agents over public business sources, scores each company and drafts outreach. Nothing is sent until a person approves it in Slack. The reliability layer is the point: four pooled model endpoints with cooldown failover and a prompt cache.

    A LangGraph supervisor routes work to a research agent that fans out across 18 data sources, a qualification agent that scores companies with retrieved context, and a sales agent that drafts a four-touch sequence. All model calls go through a pool of four endpoints with cooldown failover and a Redis prompt cache. Drafts go to Slack for a person to approve before anything is sent. SupervisorLangGraph · routesResearch18 connectors · fan-outQualifyscore 0–10 · RAG contextSalesfour-touch draftLLM poolNIM · DeepSeek · Groq · OpenRoutercooldown failover · 25 sempty 200 = failureRedis cacheprompt hash · 24 h dedupSlack reviewapprove / rejecta person decidesSendrejected → nothing leavesqueue: Celery + Redis, synchronous fallback if workers are down · warehouse: SQLite with re-runnable migrations · tracing: SSE

    2026 · Python · LangGraph · FastAPI · Celery · Redis · pgvector · case study → ·the failover code ↓

    Ledger

    ProjectWhat it isKindProofLast
    HRMS assistantRAG plus tool calling inside an HRMS product, with role-based limits on what the model may do.Employer work · private codevLLM · Langfuse · in production2026-10
    Lead research agentLangGraph agents research, score and draft outreach for B2B leads. Nothing is sent until a person approves it in Slack.Internal tool · began as a take-home219k companies · 104 routes2026-06
    Claude Profile SwitcherRun several Claude desktop accounts side by side on Windows.Open source12★ · 6 forks · 44 dlv2.0.0
    Naman Puja v1The API, storefront and admin console a puja-booking business launched on.Live product6 countries · live2026-06
    PharmaQMSPharma complaint triage where deadlines and status rules stay out of the LLM.Take-home exercise26 tests · CI green2026-07
    Brightness TrayOne brightness slider for every monitor, in a 346 KB Rust tray app.Open source5★ · 44 dl · CI 7/7v1.1.1
    WinClickerAn auto clicker that clicks inside a background window and leaves your mouse alone.Open source57 KB exe · v1.0.0v1.0.0
    AgentDeckA local, zero-dependency GUI that configures Claude Code, Codex CLI, Gemini CLI, Cursor and 17 other coding agents from one window.Open source21 agents · 65 tests · 3 OSv1.0.0
    ETHARA ↗A team task manager built in one evening: a 15-endpoint Express and SQLite API with JWT auth and per-project role checks, and a React board with a workload dashboard.take-home exerciseGitHub
    PaperGen ↗An exam paper generator. Give it a mark total and an easy, medium and hard split, and it assembles a randomised paper from a question bank and reports achieved against requested marks.small projectGitHub

    03 · Engineering notes · real code, permalinked

    Decisions, with the lines that implement them.

    Six short reads. Each quotes the actual source from one of my repositories at a pinned commit, so you can check that I did what I say.

    Note 01 · Python

    An empty 200 is a failure

    Problem. Free and cheap model endpoints sometimes return a blank completion with a success status, for example when an invalid model id is silently accepted. Treating that as success let blank results flow into scoring and drafts.

    Decision. The pool treats an empty body like a rate limit: the endpoint goes on a 25-second cooldown, its failure count goes up, and the next healthy endpoint of the same tier gets the call. Only a real completion is returned.

    AI-Lead-Generation-Research-Agent-/agents/llm_pool.py@ a254653
    def complete(self, prompt, tier: str = "strong", temperature: float = 0.2,
                 max_tokens: int = 800) -> str:
        """Call the pool with failover. Returns the completion text ("" on total
        failure). `prompt` is a string or a list of chat messages."""
        self._ensure()
        attempts = max(len(self.endpoints()), 1)
        messages = ([{"role": "user", "content": prompt}] if isinstance(prompt, str)
                    else prompt)
        last_err = None
        for _ in range(attempts):
            ep = self._pick(tier)
            if ep is None:
                logger.warning("[llm_pool] no endpoints configured")
                return ""
            try:
                resp = ep.client().chat.completions.create(
                    model=ep.model, messages=messages,
                    temperature=temperature, max_tokens=max_tokens,
                )
                content = (resp.choices[0].message.content or "").strip()
                # An empty 200 (e.g. an invalid model name the API silently accepts)
                # is a soft failure — cool the endpoint down and fail over, don't
                # hand back a blank.
                if not content:
                    last_err = f"{ep.name} returned empty content (check model id '{ep.model}')"
                    ep.cooldown_until = time.time() + _COOLDOWN_SECS
                    ep.fails += 1
                    logger.debug(f"[llm_pool] {last_err}; failing over")
                    continue
                ep.fails = 0
                return content
            except Exception as e:  # rate-limit, network, model error → cool down + next
                last_err = e
                ep.cooldown_until = time.time() + _COOLDOWN_SECS
                ep.fails += 1
                logger.debug(f"[llm_pool] {ep.name} failed ({e}); cooling down, failing over")
        logger.warning(f"[llm_pool] all endpoints exhausted: {last_err}")
        return ""
    

    Note 02 · PowerShell 5.1

    Which Claude windows are actually running?

    Problem. Electron starts renderer child processes with the same executable name as the main window, and the Claude Code CLI is also called claude.exe. A naive process list reports profiles that are not open.

    Decision. Only processes without a --type= flag and with the desktop app’s executable path count. The profile is read from the --user-data-dir argument, quoted or not, and the first match per directory wins.

    Claude-Code-Desktop-Switcher/ClaudeSwitcher.ps1@ 3199a95
    function Get-RunningProfileMap {
        # Maps a profile directory to the PID of the top-level Claude window process.
        # Child processes carry --type=renderer and friends, so they are filtered out. The
        # path check matters just as much: the Claude Code CLI is also called claude.exe,
        # the desktop app runs one per Code session, and WMI matches names case-insensitively.
        $map     = @{}
        $pattern = Get-DesktopExePattern
        $procs = Get-CimInstance Win32_Process -Filter "Name='Claude.exe'" -ErrorAction SilentlyContinue |
                 Where-Object { $_.CommandLine -and $_.CommandLine -notmatch '--type=' -and
                                $_.ExecutablePath -and $_.ExecutablePath -match $pattern }
    
        foreach ($p in $procs) {
            # Quoted paths may contain spaces (C:\Users\John Doe\...), unquoted ones cannot.
            if ($p.CommandLine -match '--user-data-dir=(?:"([^"]+)"|(\S+))') {
                $key = $(if ($Matches[1]) { $Matches[1] } else { $Matches[2] }).TrimEnd('\')
            } else {
                $key = $script:DefaultProfilePath.TrimEnd('\')
            }
            if (-not $map.ContainsKey($key)) { $map[$key] = $p.ProcessId }
        }
        return $map
    }

    Note 03 · Rust

    A sun icon from a distance function

    Problem. Brightness Tray draws its own flyout and tray icon so the binary carries no GDI+ or Direct2D dependency. Placing pixels by hand for an anti-aliased glyph at several sizes is error-prone.

    Decision. Every shape is a signed distance function. Pixel coverage is the clamped distance from the pixel centre, which gives anti-aliasing for free, and the ray length is a parameter so the icon can mirror the current brightness.

    Universal-Monitor-Brightness-control/src/gfx.rs@ e065670
    fn sd_segment(x: f32, y: f32, ax: f32, ay: f32, bx: f32, by: f32, r: f32) -> f32 {
        let (px, py, dx, dy) = (x - ax, y - ay, bx - ax, by - ay);
        let t = ((px * dx + py * dy) / (dx * dx + dy * dy)).clamp(0.0, 1.0);
        (px - dx * t).hypot(py - dy * t) - r
    }
    
    /// Sun glyph of overall `size`, centred on the origin. `rays` (0..=1) sets
    /// the ray length, so the glyph can mirror the current brightness.
    fn sun_sd(x: f32, y: f32, size: f32, rays: f32) -> f32 {
        let s = size / 2.0;
        let mut d = x.hypot(y) - s * 0.38;
        let t = s * 0.095;
        let r0 = s * 0.6;
        let r1 = r0 + s * (0.06 + 0.24 * rays.clamp(0.0, 1.0));
        for k in 0..8 {
            let (sn, cs) = (k as f32 * FRAC_PI_4).sin_cos();
            d = d.min(sd_segment(x, y, cs * r0, sn * r0, cs * r1, sn * r1, t));
        }
        d
    }
    
    /// Straight-alpha ARGB pixels for a `size`×`size` sun icon in colour `rgb`.
    pub fn sun_icon(size: i32, rgb: u32) -> Vec<u32> {
        let c = size as f32 / 2.0;
        (0..size * size)
            .map(|i| {
                let (x, y) = ((i % size) as f32 + 0.5 - c, (i / size) as f32 + 0.5 - c);
                let a = cov(sun_sd(x, y, size as f32, 1.0));
                (((a * 255.0 + 0.5) as u32) << 24) | rgb
            })
            .collect()
    }

    Note 04 · C#

    Clicking a window that is behind another one

    Problem. Sending a click through the cursor steals the mouse. WinClicker had to click inside a window that may be covered, while the person keeps using their machine.

    Decision. Walk down from the target window to the deepest child under the chosen point, convert the point to that child’s client space, then post the mouse messages straight to it. The cursor never moves.

    WinClicker/App.cs@ 202d02e
    Native.RECT rc;
    Native.GetWindowRect(Root, out rc);
    var pt = new Native.POINT { X = rc.Left + X, Y = rc.Top + Y };
    
    IntPtr h = Root;
    for (int depth = 0; depth < 32; depth++)
    {
        Native.POINT c = pt;
        Native.ScreenToClient(h, ref c);
        IntPtr child = Native.ChildWindowFromPointEx(h, c, Native.CWP_SKIPINVISIBLE | Native.CWP_SKIPDISABLED | Native.CWP_SKIPTRANSPARENT);
        if (child == IntPtr.Zero || child == h) break;
        h = child;
    }
    
    Native.POINT cp = pt;
    Native.ScreenToClient(h, ref cp);
    IntPtr lp = (IntPtr)((cp.Y << 16) | (cp.X & 0xFFFF));
    Native.PostMessage(h, Native.WM_MOUSEMOVE, IntPtr.Zero, lp);
    Native.PostMessage(h, DownMsg, (IntPtr)MkFlag, lp);
    Native.PostMessage(h, UpMsg, IntPtr.Zero, lp);
    if (Double)
    {
        Native.PostMessage(h, DblMsg, (IntPtr)MkFlag, lp);
        Native.PostMessage(h, UpMsg, IntPtr.Zero, lp);
    }

    Note 05 · JavaScript (Node)

    A guard that blocks rm -rf / but not a commit message about it

    Problem. AgentDeck’s hook recipe has to stop an agent from running destructive shell commands. A regex over the whole command line would also fire on a commit message or grep pattern that merely mentions rm -rf, and the agent would be blocked for nothing.

    Decision. The line is split into simple commands at ; | & and newlines, respecting quotes, then each command into words that remember whether they were quoted. Only the unquoted leading word and its flags are judged, so text inside an argument is never mistaken for the command.

    AI-harness-Character-creator/lib/recipes/guard-destructive.js@ e4f1943
    // (a commit message, a grep pattern) is never mistaken for the command itself.
    function words(segment) {
      const out = [];
      const re = /"((?:[^"\\]|\\.)*)"|'([^']*)'|(\S+)/g;
      let m;
      while ((m = re.exec(segment))) out.push({ text: m[1] != null ? m[1] : m[2] != null ? m[2] : m[3], quoted: m[3] == null });
      return out;
    }
    
    function segments(command) {
      const out = [];
      let cur = '';
      let quote = null;
      for (let i = 0; i < command.length; i++) {
        const c = command[i];
        if (quote) { cur += c; if (c === '\\' && quote === '"') cur += command[++i] || ''; else if (c === quote) quote = null; continue; }
        if (c === '"' || c === "'") { quote = c; cur += c; continue; }
        if (c === ';' || c === '\n' || c === '|' || c === '&') { out.push(cur); cur = ''; continue; }
        cur += c;
      }
      out.push(cur);
      return out;
    }
    
    // A root, home folder, drive, the current or a parent folder, optionally with a trailing
    // slash or wildcard:  /  /*  ~  ~/  $HOME/  .  ./*  ../..  *  C:\  C:\*

    Note 06 · TypeScript

    The CRM must never block a booking

    Problem. Every booking on namanpuja.com is mirrored into the sales team’s CRM as a contact and a deal. The CRM is a separate service, and a customer should never see an error because it is slow or down.

    Decision. The sync runs after the booking is saved, is wrapped end to end, and returns null on any failure. Secondary writes swallow their own errors. The booking flow only ever learns “synced” or “not synced”.

    backend-namanpuja/src/services/crm/atomicCrm.ts@ 3589099
    export async function syncBookingToCrm(
      booking: BookingForCrm,
    ): Promise<{ contactId: number; dealId: number } | null> {
      if (!env.crm.enabled) return null;
      if (!env.crm.supabaseUrl || !env.crm.serviceRoleKey) {
        // eslint-disable-next-line no-console
        console.warn('[crm] enabled but SUPABASE_URL / SERVICE_ROLE_KEY missing — skipping sync');
        return null;
      }
    
      try {
        const { first, last } = splitName(booking.customerName);
    
        const contact = await crmPost<CrmContactResult>('contacts', {
          first_name: first,
          last_name: last,
          // Atomic CRM stores email/phone as JSONB arrays of { type, value }
          email_jsonb: [{ type: 'Work', email: booking.customerEmail }],
          phone_jsonb: [{ type: 'Work', number: booking.customerPhone }],
          background: `Inbound booking ${booking.reference} from namanpuja.com`,
          sales_id: env.crm.defaultSalesId,
          tags: [],
          status: 'hot',
        });
    
        const dealName = [booking.pujaName, booking.cityName].filter(Boolean).join(' — ') ||
          `Booking ${booking.reference}`;
    
        const deal = await crmPost<CrmDealResult>('deals', {
          name: dealName,
          contact_ids: [contact.id],
          category: booking.serviceType,
          stage: 'opportunity',
          amount: booking.amount ?? 0,
          description:
            `Reference: ${booking.reference}\n` +
            `Service: ${booking.serviceType}\n` +
            (booking.preferredDate ? `Preferred date: ${booking.preferredDate.toISOString()}\n` : '') +
            (booking.notes ? `Notes: ${booking.notes}` : ''),
          sales_id: env.crm.defaultSalesId,

    04 · Tools you can run today

    Four tools you can run today. Free, open source.

    I write these when something on my own machine annoys me. Three are single-file Windows utilities; AgentDeck is a zero-dependency Node app. Source for all of them is on GitHub.

    ToolDoesVersionSizeDownloads
    Claude Profile SwitcherSeveral Claude desktop accounts, open at the same time.v2.0.0 · 2026-09-301 KB installer, 69 KB script44Download ↓
    Brightness TrayA brightness slider for every monitor you have plugged in.v1.1.1 · 2026-09-30346 KB exe44Download ↓
    AgentDeckOne window to configure Claude Code, Codex, Gemini CLI, Cursor and 17 other coding agents.v1.0.0 · 2026-10-06548 KB zip, no dependencies—Download ↓
    WinClickerClicks inside a background window while you keep working.v1.0.0 · 2026-10-0457 KB exe—Download ↓

    Install notes, checksums and screenshots →

    05 · Experience

    Where I’ve worked.

    1. Jun 2026 – nowNew Delhi

      AI Architect · RazorInfoTech

      • Put an AI assistant inside the company’s HRMS product: it answers HR policy questions with citations and runs real actions (leave balances, attendance, ticket creation) through tool calling, limited by each user’s role.
      • Host open-source LLMs on cloud GPUs with vLLM, and run Langfuse for tracing, token and cost tracking, and latency and answer-quality monitoring.
      • Build automations for HR, operations, sales and support teams, including the lead research pipeline described below.

      Read the HRMS assistant case study →

    2. Jan – May 2026New Delhi

      AI Full Stack Developer (intern) · Webisdom Management Services

      I took an internship to move fully into LLM work after three years as a software and web developer.

      • Built retrieval-augmented generation pipelines in Python with LangChain and LlamaIndex: extraction, cleaning, semantic chunking, embeddings, FAISS indexing, retrieval with re-ranking, and checks for hallucination and citation accuracy.
      • Shipped conversational and summarisation features on the OpenAI and Anthropic APIs behind FastAPI, with async handling, rate-limit management and structured output parsing.
      • Led a team of four interns. Met 6 of 6 KRAs in February 2026, five of them rated Excellent.
    3. May 2024 – May 2025Remote

      Web Developer · Ascent Edge

      Performance marketing agency.

      • Built the training and deployment pipeline for the agency’s attribution and conversion-prediction models: Dockerised jobs, GitHub Actions for retraining and release, monitoring for drift and latency.
      • Built web applications in React and TypeScript that loaded 35% faster and lifted conversion by 18% in A/B tests.
    4. May 2023 – Apr 2024Toronto (remote)

      Software Developer · eSentire

      Managed detection and response, cybersecurity.

      • Built NLP classification pipelines in Python that route threat alerts, cutting manual processing by about 20%.
      • Ran the surrounding engineering: REST APIs in Python and Node.js, CI/CD, Docker deployments on AWS, tests and code review.
    5. Aug 2022 – Mar 2023Bhopal

      Software Developer · SPF

      • Developed and maintained web applications, including QA and security testing.
    6. Dec 2021 – Mar 2022Toronto

      Business Analyst Intern · Seneca College (SSF)

      • Data analysis and stakeholder reporting for process improvement.

    Full resume (PDF) →

    06 · About

    The short version.

    My first AI work was at eSentire, a cybersecurity company, where I got machine learning models to sort threat alerts so analysts didn’t have to. After a year building web apps and model pipelines for a marketing agency, I went all in on LLM work.

    What I enjoy is the unglamorous half of AI engineering: retrieval that returns the right passage, tools the model can call without doing damage, fallbacks for when an endpoint dies, and traces that tell you what happened. A demo is easy to make. I like the part after the demo.

    I use AI coding tools every day and say so. They make me faster. Deciding what to build, and checking that it is right, is still my job.

    Education

    • M.Sc. Computer ScienceLiverpool John Moores University · 2023 – 2025
    • PG Diploma, Software DevelopmentIIIT Bangalore · 2023 – 2024
    • PG Diploma, International Business ManagementSeneca College, Toronto · 2021 – 2022
    • B.Sc. Computer Science and Hotel AdministrationIHM Pusa, New Delhi · 2016 – 2019

    What I work with

    LLM applications
    RAG pipelines, Tool / function calling, LangGraph, LangChain, LlamaIndex, OpenAI and Anthropic APIs, Prompt design and evaluation, Hugging Face Transformers
    Serving and observability
    vLLM, Langfuse, FAISS, pgvector, ChromaDB, Redis, Celery
    Backend
    Python, FastAPI, Node.js, Express, TypeScript, PostgreSQL, MySQL, MongoDB, SQLite
    Frontend
    React, Next.js, Astro, HTML and CSS
    Shipping
    Docker, GitHub Actions, AWS, Azure, Vercel
    Windows and systems
    Rust, C# / WPF, PowerShell, Win32 API

    07 · For founders and teams

    If you need something built.

    Alongside full-time roles I take a small number of contract projects. These are the three I’m best placed to do, because I’ve done each before.

    1. An assistant that knows your documents and can act

      Answers grounded in your own policies or knowledge base, with citations. Connected to your systems so it can look things up and do things, inside the permissions you set.

      Built for an HR product →

    2. A research or operations pipeline with a human in the loop

      Agents that gather, score and draft, then stop and wait for a person to approve. Built to keep running when a model provider has a bad day.

      Like the lead research pipeline →

    3. The first version of a product, in weeks

      API, web front end, admin panel and the integrations around them, set up so another engineer can pick it up and keep going.

      Like the Naman Puja launch →

    How it goes. You email me what you’re trying to do. We talk for half an hour. I send a short written scope with a fixed first milestone. You see working software every week.

    08 · Contact

    Hiring, or have a project? Write to me.

    priyanshu.py98@gmail.com

    It helps if you say what the role or project is, the timeline, and where you’re based.