I Accidentally Built an AI Employee Out of Scripts and Bad Sleep Habits
The kitchen table had become infrastructure without anyone formally deciding it should. Two laptops sat open because one had quietly developed thermal problems months earlier and now worked better when left mostly alone. There was dust trapped under keycaps, tangled USB cables wrapped around a cheap mouse, and a notebook filled with diagrams that looked increasingly less like project planning and more like someone mapping utility lines under a city. The apartment was warm in the way apartments get when machines have been running for days. Not dangerously warm. Just enough that you notice when you walk back into the room. I woke up because the fan noise never stopped. That was unusual. Before going to sleep, I had queued a few repository checks and left some scripts running because I wanted documentation updates waiting for me in the morning. Nothing ambitious. Just housekeeping. Overnight, though, those scripts had triggered other scripts. Logs generated summaries. Repository changes triggered review tasks. An AI model categorized failures, updated markdown files, and generated issue notes for problems I had completely forgotten existed. The strange part was not that it worked. The strange part was realizing I had slowly crossed a line where work continued happening after I stopped participating. Nobody sets out intending to build an AI employee. The phrase itself creates the wrong picture. It makes people imagine artificial coworkers replacing humans, glowing dashboards, or expensive orchestration diagrams with arrows pointing everywhere. In practice, the systems that become genuinely useful usually emerge from irritation. You get tired of repeating something. Then you automate it. Then the automation creates another annoying bottleneck. Then you automate that too. Eventually you wake up surrounded by scripts that know your workflow better than some coworkers do. That process was gradual enough that I barely noticed it happening. Repetition Is Expensive in Ways That Are Hard to Notice Before building any of this, my workflow had become bloated with small acts of reconstruction. Open repositories. Check test results. Read logs. Copy information into notes. Open an LLM. Paste context. Forget context. Reconstruct context. Repeat. None of these tasks individually felt substantial. Together they consumed entire afternoons. This is one of the uncomfortable things about modern technical work. The exhausting part is often not the complexity. It is the context switching. Every transition between systems creates overhead. Every dashboard, notification, browser tab, and disconnected note creates tiny taxes on attention. AI tools can actually worsen this stage initially because they dramatically increase output while leaving workflow structure untouched. You suddenly generate more code, more documentation, more ideas, more summaries, and more tasks without creating systems to contain them. For a while I was producing information faster than I could metabolize it. That forced a different question. Not: “How do I make the model better?” Instead: “Why am I still manually touching this step?” That question turned out to be dangerous because nearly every repeated action started looking suspicious. The First Useful Automation Was Almost Embarrassingly Small People often expect a turning point story here involving some sophisticated agent framework. It was a shell script. That script did four things. Run tests. Collect outputs. Store logs. Generate summaries. That was enough. Not because the script itself was powerful, but because it introduced persistence into places where persistence did not exist before. Soon another script checked dependencies across projects. Another scanned repositories for stale TODO comments. Another watched directories and categorized outputs. Scheduled tasks started running overnight because unused CPU time felt wasteful. I added notifications only for unusual events because constant alerts train you to ignore alerts entirely. Eventually I realized the individual scripts mattered less than the relationships between them. Automation systems rarely become useful through intelligence alone. They become useful through continuity. Schedulers matter. Storage matters. Logging matters. Boring infrastructure matters more than people want it to. Cron jobs are not glamorous. Filesystem watchers are not glamorous. Append only logs are not glamorous either. Still, these simple pieces create something important: work that persists without requiring continuous attention. An AI Employee Is Mostly Scheduling Wearing a Fancy Hat The phrase “AI employee” survived because it is marketable. The reality is much less cinematic. What people actually need is usually persistent labor. A useful automation system notices events, performs constrained tasks, stores outputs, and surfaces exceptions. That is closer to what most teams require than some fully autonomous digital coworker wandering around repositories making independent decisions. My setup eventually stabilized into something like this: Repository activity triggered watchers. Watchers triggered scripts. Scripts gathered information and passed constrained tasks to models. Results entered storage layers where later scripts could categorize or summarize them. Notifications only appeared when thresholds were crossed. Notice how little of that description involves prompting. Prompting culture sometimes treats language models as the center of the universe. Infrastructure quietly determines whether those outputs become useful or disappear into folders you never reopen. The more systems I built, the more obvious this became. Memory beats intelligence surprisingly often. Overnight Systems Change Your Relationship With Time The first genuinely unsettling moment happened after setting up overnight repository sweeps. I woke up expecting maybe a few reports. Instead there were dozens. Documentation updates. Dependency warnings. Suggested refactors. Issue summaries. Risk rankings. Generated notes explaining architectural weaknesses I had forgotten existed. Some recommendations were excellent. Some were nonsense. One confidently suggested removing code responsible for authentication because it misinterpreted usage patterns. That experience permanently changed how I think about autonomous systems. People talk about AI mistakes as if mistakes are exceptional. Mistakes are the operating environment. The goal is not creating systems that avoid failure. The goal is building systems where failure remains visible. That requires review layers. Stored outputs. Audit trails. Approval checkpoints. The moment automation becomes invisible, reliability starts degrading. Machines repeat errors more consistently than humans do. That consistency is useful if you can observe it. Dangerous if you cannot. Physical Spaces Quietly Reshape Technical Systems Something else changed that I did not expect. The room changed. Folders became cleaner because messy storage created automation failures. Desk layout changed because notifications constantly entering peripheral vision became exhausting. I separated monitoring screens from active work screens. Started keeping handwritten checkpoints because physical notes created friction against impulsive task switching. Bought cheap notebooks specifically because expensive notebooks made me weirdly protective of blank pages. These details sound unrelated until you live inside automated systems long enough. Interfaces train behavior. Physical environments train behavior too. When your projects operate continuously, organization stops being aesthetic preference and becomes system reliability. Small environmental choices become infrastructure. The Mistakes Were More Educational Than the Successes One automation loop accidentally generated documentation updates using stale assumptions for several days. Another duplicated issue reports so aggressively that repositories became harder to navigate afterward. I once built a notification system that sent updates for everything because more visibility sounded useful. After two weeks I had trained myself to ignore notifications entirely. Failure patterns taught more than successful runs ever did. A few rules survived repeated mistakes: Keep raw outputs separate from approved outputs. Timestamp everything. Build kill switches. Prefer append only logs. Constrain scope aggressively. These principles sound boring because they are. Most reliability practices are boring. That is partly why people skip them. Bad Sleep Habits Were Not the Solution, But They Exposed the Problem I would love to pretend this system emerged through disciplined optimization. It mostly emerged through accumulated annoyance and poor sleep. Fatigue changes your tolerance for friction. Repeated actions become unbearable faster. Opening the same dashboards every morning started feeling absurd. Rebuilding project context repeatedly felt absurd. Discovering failures hours late felt absurd. Exhaustion exposed inefficiencies that motivation had previously hidden. That does not make sleep deprivation useful. It makes friction easier to notice. The actual solution was building systems that reduced dependence on constantly available attention. Human focus fluctuates. Projects do not stop existing when focus disappears. Persistent systems help bridge that gap. Start Smaller Than You Think You Need People consistently begin automation projects at the wrong scale. Multi agent research swarms. Autonomous startup operators. Complex orchestration graphs. Meanwhile, documentation remains outdated and dependency updates go unchecked. Start with one task. One repeated annoyance. One responsibility. Create something that reviews pull requests nightly. Summarizes logs. Categorizes research notes. Generates documentation snapshots. Then leave it running. Observe failure patterns. Expand slowly. The useful systems rarely arrive fully formed. They accumulate. Right now, writing this, several scripts are running in the background. Not because I particularly enjoy automation theater. Mostly because somewhere between repository watchers, scheduled jobs, and piles of generated reports, I realized completed work waiting in the morning changes how projects feel. Projects stop depending entirely on your current energy level. That shift is subtle at first. Then one day the laptop fan is still running when you wake up, the machine spent the night organizing problems you forgot existed, and the line between tools and coworkers becomes slightly harder to locate than you expected.
How the 80/20 Rule and GitHub Copilot Saved My Abandoned App From Code Graveyard
GitHub “Finish-Up-A-Thon” Challenge Submission This is a submission for the GitHub Finish-Up-A-Thon Challenge What I Built AlkaFocus is a high-performance, mobile-first productivity engine engineered to help developers focus on what truly moves the needle. Far too many productivity apps treat all tasks equally, leading to cognitive fatigue and developers drowning in low-impact administrative work. Built with React Native (Expo Router), TypeScript, Zustand, and NativeWind (Tailwind CSS) , AlkaFocus bridges the gap between structured time management and the 80/20 Pareto Principle. It forces you to isolate the critical 20% of engineering effort that yields 80% of your real results, tracking your daily momentum in real-time. Demo Try the Code GitHub Repository: alphonsekazadi / AlkaFocus AlkaFocus A productivity engine for developers that enforces the 80/20 rule, surfacing the top 20% of high-impact work and eliminating burnout through disciplined focus. Overview AlkaFocus is a mobile-first productivity app built to help developers isolate and execute the most critical tasks. It is designed around a modern three-tab native experience, delivering a premium dashboard, a focused Pomodoro timer workflow, and actionable performance analytics. Core Architecture Built with a strong native-first architecture and scalable React tooling: React Native + Expo : Native mobile performance with Expo SDK stability. Expo Router : Native tab-based routing with a declarative file-system structure. TypeScript : Strong typing throughout the app. Zustand : Lightweight state management with persistent storage. AsyncStorage : Offline persistence to retain task state and app preferences. NativeWind : Tailwind-style styling for fast UI polish. EAS Build : Production-ready Android APK distribution configuration. High-Impact Feature Breakdown Core Experience Three-Tab Architecture Home / … View on GitHub Expo Sandbox / Production APK: https://expo.dev/accounts/alphonsekazadi/projects/AlkaFocus/builds/0035e608-fa6b-49f4-946a-87148bc5cbe5 2.📱 See It in Action The Comeback Story Where it Started AlkaFocus began as a personal passion project—an offline utility script that sat abandoned and unpolished in my GitHub repositories. It lacked routing, lacked state management persistence, and suffered from a rigid, non-scannable design layout. It was a classic “half-finished dev project” buried under a stack of Master’s degree studies and software engineering workflows. The Transformation The GitHub Finish-Up-A-Thon was the ultimate catalyst to revive it. Over an intense development sprint, I refactored the entire architecture from the ground up: State Store Overhaul : Replaced local reactive loops with a persistent state engine via Zustand and AsyncStorage to ensure user task data safely survives device reloads. Native Layout Engineering : Migrated to Expo Router to structure a smooth, three-tab modular layout pipeline (Timeline, Timer, Review) with dynamic custom tab bars. Hardware Styling Upgrades : Built production-grade, hardware-pinned headers that stay perfectly static while views scroll smoothly beneath them. A Fully Fluid Dynamic UI : Programmed an instant Dark/Light mode toggle system that updates the entire visual language in real-time, complete with a hardware StatusBar listener to flip phone system icons dynamically on iOS and Android devices. My Experience with GitHub Copilot As a developer balancing software architecture and cybersecurity research, speed and focus are my primary levers. GitHub Copilot acted as a top-tier pair-programmer throughout this entire crunch period, dramatically accelerating my development velocity. How Copilot Accelerated the Sprint: Context-Aware Tailwind Layouts: Copilot instantly accurately predicted deep utility string tokens for NativeWind, allowing me to build premium dark slate blocks (bg-slate-900) and handle light theme overrides without breaking native layout trees. Algorithmic Precision for Pareto Insights : Writing the conditional analysis logic to evaluate a user’s performance arc was effortless. Copilot helped me seamlessly parse array elements inside the Zustand store to compute exact data insights, calculating if a developer is actually focusing on high-impact sprints or burying momentum in administrative noise. Refactoring Safety: When transitioning from a standard standard page routing configuration to a slide-up native presentation sheet modal (presentation: “modal”), Copilot helped me correctly map my core app layout tree constraints, saving me hours of local bundler troubleshooting. Thanks to Copilot, I cut boilerplate setup by an estimated 70%, enabling me to focus 100% of my cognitive energy on architectural design and mobile user experience.
The Complete Developer’s Guide to the Baileys WhatsApp Bot: Setup, Scaling, and VPS Deployment
WhatsApp has become the default operating system for daily communication in regions like India. For modern web platforms—particularly in EdTech, local logistics, or localized services—forcing users to log into a complex desktop portal often results in a steep drop-off in user engagement. When building LoopLearnX (an automated homework evaluation and tutoring tool for CBSE students), we realized that students rarely log in to a web dashboard on a desktop to upload their homework. Instead, they do their homework on physical notebooks, snap a picture, and expect instant grading. Integrating a custom, self-hosted WhatsApp interface directly into our Next.js application was not just a convenience—it was the single most critical driver of student engagement. This guide details the technical blueprint of how we built a resilient, memory-aware WhatsApp AI Bot using @whiskeysockets/baileys and Next.js, hosted on an Oracle Cloud VPS. We will cover the exact production failures we encountered, learnings learned, and why custom self-hosting beats off-the-shelf agent frameworks. 🚀 1. Why WhatsApp & Baileys? The Engagement Multiplier For many demographics, WhatsApp represents friction-free engagement. Users don’t need to remember passwords, manage active sessions, or learn a new user interface. By bringing our platform inside a messaging channel, we instantly enabled frictionless student homework submissions. The Bot Core: Why Baileys? To connect an application to WhatsApp, you have two primary routes: The Official WhatsApp Business Cloud API: Extremely restrictive, expensive (per-conversation pricing), and requires Facebook Business Verification. It strictly forbids sending arbitrary free-form text or non-template messages outside a 24-hour window. Baileys ( @whiskeysockets/baileys ): A high-performance, headless, WebSocket-based implementation of the WhatsApp Web protocol. It allows you to programmatically control a WhatsApp account (including standard consumer or business accounts) with full messaging flexibility, zero per-message charges, and native support for modern features like multi-file authentication state. The Hybrid Architecture To keep operations lightweight, we split the application into a two-tier architecture : The Gateway (Ubuntu VPS): Runs a lightweight Node.js daemon using Baileys to maintain WebSocket connections with WhatsApp servers 24/7. It listens to incoming messages, handles media download streams, and converts payloads into clean base64 data to pass forward. The Logic Engine (Vercel Serverless): A secure Next.js API route that handles heavy database transactions (Supabase), state transitions, and LLM evaluations (Gemini-2.5-Flash). [Student WhatsApp] │ ▼ (WebSocket 24/7 connection) [Node.js VPS Gateway (Baileys + PM2)] │ ▼ (HTTP POST with x-bot-secret) [Next.js Serverless Route (Vercel)] ├── 1. Authenticate Request ├── 2. Query Student Profile & History (Supabase) ├── 3. Classify & Evaluate Intent (Gemini API) └── 4. Write new Submission Record (Supabase) │ ▼ (JSON Reply) [Node.js VPS Gateway (Safe Queued Output)] ──► Sent back to Student WhatsApp Enter fullscreen mode Exit fullscreen mode 🛠️ 2. Step-by-Step Code Walkthrough Part A: Setting up the Baileys Client ( index.js ) The core responsibilities of index.js on the VPS are maintaining the WebSocket session, managing authentication states, rendering QR codes for linking, and mounting an Express endpoint to monitor status. // index.js require ( ” dotenv ” ). config (); const { default : makeWASocket , useMultiFileAuthState , DisconnectReason , } = require ( ” @whiskeysockets/baileys ” ); const { Boom } = require ( ” @hapi/boom ” ); const pino = require ( ” pino ” ); const express = require ( ” express ” ); const qrcodeTerminal = require ( ” qrcode-terminal ” ); const qrcode = require ( ” qrcode ” ); const { handleIncomingMessage } = require ( ” ./bridge ” ); const app = express (); const PORT = process . env . PORT || 3000 ; let sock = null ; let botStatus = ” starting ” ; let currentQrImage = null ; async function connectToWhatsApp () { // 1. Initialize multi-file authentication state const { state , saveCreds } = await useMultiFileAuthState ( ” auth_info_baileys ” ); sock = makeWASocket ({ auth : state , printQRInTerminal : false , // We render custom QR inside terminal & web UI logger : pino ({ level : ” silent ” }), }); // 2. Listen for connection state updates sock . ev . on ( ” connection.update ” , async ( update ) => { const { connection , lastDisconnect , qr } = update ; if ( qr ) { botStatus = ” qr_needed ” ; // Render QR in terminal qrcodeTerminal . generate ( qr , { small : true }); // Generate Data URL QR for web UI status page currentQrImage = await qrcode . toDataURL ( qr ); } if ( connection === ” close ” ) { const shouldReconnect = lastDisconnect ?. error instanceof Boom ? lastDisconnect . error . output ?. statusCode !== DisconnectReason . loggedOut : true ; botStatus = shouldReconnect ? ” disconnected ” : ” logged_out ” ; console . log ( ” Connection closed. Reconnecting… ” , shouldReconnect ); if ( shouldReconnect ) { connectToWhatsApp (); } } else if ( connection === ” open ” ) { botStatus = ” connected ” ; console . log ( ” ✅ WhatsApp WebSocket Connected successfully! ” ); } }); // 3. Save updated credentials on session changes sock . ev . on ( ” creds.update ” , saveCreds ); // 4. Mount incoming message listener sock . ev . on ( ” messages.upsert ” , async ( m ) => { if ( m . type === ” notify ” ) { for ( const msg of m . messages ) { if ( ! msg . key . fromMe ) { await handleIncomingMessage ( sock , msg ); } } } }); } // Simple web UI endpoint for linking & status monitoring app . get ( ” / ” , ( req , res ) => { res . send (
LoopLearnX Bot Status
Current Status: ${ botStatus }
${ botStatus === " qr_needed " && currentQrImage ? : "" } ); }); app . listen ( PORT , () => { console . log ( Express status server running on port ${ PORT } ); connectToWhatsApp (); }); Enter fullscreen mode Exit fullscreen mode Part B: Creating a Resilient Message Handler ( bridge.js ) The bridge.js file handles payload filtering, captures typed text, and handles complex media streams. One of the biggest issues in production is text messages arriving empty at Vercel. WhatsApp packs text differently based on messaging schemas. We wrote a nested parser that extracts text under all possible client payloads. Additionally, when receiving an image, the bot downloads the file buffer, converts it to base64, and triggers our serverless endpoint: // bridge.js const axios = require ( ” axios ” ); const { downloadMediaMessage } = require ( ” @whiskeysockets/baileys ” ); const API_URL = process . env . LOOPLEARN_API_URL ; const BOT_SECRET = process . env . WHATSAPP_BOT_SECRET ; async function handleIncomingMessage ( sock , msg ) { const jid = msg . key . remoteJid ; if ( ! jid || jid . endsWith ( ” @g.us ” )) return ; // Skip group chats const phone = jid . replace ( ” @s.whatsapp.net ” , “” ); const content = msg . message ; const imageMsg = content ?. imageMessage ; const isText = !! ( content ?. conversation || content ?. extendedTextMessage ?. text ); // 1. Text Message Processing Route if ( isText ) { const textBody = content ?. conversation || content ?. extendedTextMessage ?. text || “” ; if ( ! textBody . trim ()) return ; await callApi ( ” /api/whatsapp/receive ” , { phone , messageType : ” text ” , textBody : textBody . trim (), }) . then (( data ) => { if ( data ?. replyText ) queueMessage ( sock , jid , data . replyText ); }) . catch (() => { queueMessage ( sock , jid , ” ⚠️ System check failed. Please try again. ” ); }); return ; } // 2. Multimodal Photo Homework Route if ( imageMsg ) { queueMessage ( sock , jid , ” 📸 Photo mila! Evaluate ho raha hai… thodi der ruko. ⏳ ” , ); let imageBuffer ; try { // Securely download the encrypted media buffer from WhatsApp servers imageBuffer = await downloadMediaMessage ( msg , ” buffer ” , {}); } catch ( e ) { console . error ( ” Image download error: ” , e . message ); queueMessage ( sock , jid , ” ❌ Photo download fail. Please try again. ” ); return ; } const imageBase64 = imageBuffer . toString ( ” base64 ” ); const mimeType = imageMsg . mimetype || ” image/jpeg ” ; await callApi ( ” /api/whatsapp/receive ” , { phone , imageBase64 , mimeType , messageType : ” image ” , }) . then (( data ) => { const reply = data ?. replyText ?? ” ⚠️ Evaluation failed. Dobara try karo. ” ; queueMessage ( sock , jid , reply ); }) . catch (( e ) => { console . error ( ” API error: ” , e . message ); queueMessage ( sock , jid , ” ⚠️ Server connection timeout. Please try again. ” , ); }); return ; } } async function callApi ( path , body ) { const res = await axios . post ( ${ API_URL }${ path } , body , { headers : { ” Content-Type ” : ” application/json ” , ” x-bot-secret ” : BOT_SECRET , }, timeout : 90000 , // 90-second timeout — Gemini Vision can be slow }); return res . data ; } Enter fullscreen mode Exit fullscreen mode 🚫 3. Crucial: Solving the \”Ban & Crash\” Problem (Rate-Limiting Queues) If your bot sends multiple API calls instantly to the same recipient or pushes bulk updates simultaneously, WhatsApp will trigger a session ban. We mitigated this risk using an asynchronous, rate-limited memory queue: const sendQueue = []; let sending = false ; function queueMessage ( sock , jid , text ) { sendQueue . push ({ jid , text }); processSendQueue ( sock ); } async function processSendQueue ( sock ) { if ( sending || ! sendQueue . length ) return ; sending = true ; while ( sendQueue . length ) { const { jid , text } = sendQueue . shift (); try { await sock . sendMessage ( jid , { text }); } catch ( e ) { console . error ( ” WebSocket send error: ” , e . message ); } // Artificial delay mimicking natural human interaction patterns await sleep ( 1500 + Math . random () * 1500 ); } sending = false ; } Enter fullscreen mode Exit fullscreen mode 💡 4. Production VPS Deployment & Management To run the Node.js Baileys gateway in a professional VPS environment, you must secure your server with PM2 process monitors and fail-safes.
Step 1: Install VPS Dependencies Connect to your Ubuntu server: sudo apt update
&& sudo apt upgrade -y curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash – sudo apt install -y nodejs sudo npm install -g pm2 Enter fullscreen mode Exit fullscreen mode
Step 2: PM2 Configuration ( ecosystem.config.js ) Create a custom configuration file
Warning: You must run only 1 instance to prevent authorization lock conflicts: // ecosystem.config.js module . exports = { apps : [ { name : ” looplearnX-bot ” , script : ” index.js ” , instances : 1 , // DO NOT USE MAX (Cluster mode breaks Baileys) autorestart : true , watch : false , max_memory_restart : ” 500M ” , restart_delay : 5000 , // Wait 5s before rebooting on crash env : { NODE_ENV : ” production ” , }, }, ], }; Enter fullscreen mode Exit fullscreen mode Start the bot and make it persistent across system updates: pm2 start ecosystem.config.js pm2 save pm2 startup Enter fullscreen mode Exit fullscreen mode To monitor logs and check performance status: pm2 logs looplearnX-bot pm2 status Enter fullscreen mode Exit fullscreen mode 🧠 Why Build Custom Instead of Using Off-the-Shelf Agents (Hermes, Landbot)? When setting up a WhatsApp integration, many teams consider wrapper services like Hermes, Coze, or standard flow builders like Landbot. Here is a technical breakdown of why we rejected off-the-shelf agents in favor of a custom Baileys/Next.js stack: Evaluation Metric Off-The-Shelf Agents (e.g. Hermes, Landbot) Custom Self-Hosted Stack (Baileys + Next.js) API & Database Integration Restricted to webhooks and limited UI components. Direct access to server-side Postgres (Supabase client), executing transactions natively. Memory Architecture Generic system chat history (context window size limitations). Custom Memory Context Routing . We query previous attempts for that exact homework plan ID and feed that specific context straight to Gemini. Hinglish & Direct Tone Tuning Very hard to enforce strict localized prompt guidelines consistently. Full controller prompts. The model speaks in second-person direct Hinglish ( “Aapne” instead of “Student ne” ). Pricing Scaling Per-message/per-run markup pricing (can grow to thousands of dollars). $0 SaaS Fees. You only pay for a $3 VPS (Oracle/Hetzner) and raw token consumption on Gemini API. Summary Integrating the Baileys WhatsApp Bot with Next.js on an Oracle Cloud VPS completely transformed the adoption curve of our LoopLearnX EdTech platform. Instead of fighting friction on desktops, students now have an active personal AI tutor in their pockets. Self-hosting using Baileys gives you total database sovereignty, complete control over token pricing, and the ability to customize your conversational workflows with zero platform restrictions. The key to operational success is keeping your VPS thread-safe, deploying rate-limited queues, and handling serverless timeout boundaries gracefully. Naveen Gaur is a WordPress Performance Specialist & Full-Stack Consultant specializing in speed optimization, Core Web Vitals, and technical audits for high-performance websites. Naveen Gaur | WordPress Performance Specialist & Full-Stack Consultant WordPress Performance Specialist & Full-Stack Consultant | Technical SEO · Emergency Recovery · Custom Web Apps | Helping Founders Fix What Others Can’t naveengaur.com
An LLM API call, in 4 GIFs
Statelessness and cost-saving tips This is the first post of series Building TinyAgent where we are going to build a small agent from scratch in Node.js with no frameworks just the API calls. But before we write an agent, we need to understand what actually happens when you call an LLM. If you’ve only ever used a SDK, you’ve probably never seen the raw request and understand how it works. Six lines of code, an API key, and it just works but you have no idea what happened when request was dispatched and response was printed on the screen. 1. The request Here is the sample API call with each and every section explained in detail. A few things worth noticing in the API call. The API is stateless : Every new API call does not remember previous call context. If you want a chatbot that “remembers” earlier messages, you hold the messages array and resend the whole thing every time. max_tokens is a hard stop, not a target. If you hit the target the response stops mid sentence. The API call pattern is universal. Different URL, Authorization: Bearer instead of x-api-key, the system prompt lives inside messages rather than at the top level. But it’s the same POST, the same JSON, the same {model, messages, max_tokens}. Once you understand the shape, switching providers is just a find-and-replace. 2. The response The API answers with a JSON blob. There are ~10 fields in it, but only four actually matter: The one which is mostly skipped is: stop_reason . It tells you why the model stopped, and in real systems and there could be possible reasons behind it: end_turn → finished naturally, you’re done max_tokens → hit your ceiling, response is truncated tool_use → model wants to call a tool (next post!) stop_sequence → matched one of your stop strings Enter fullscreen mode Exit fullscreen mode If you only check the text and ignore stop_reason , you will ship a bug at some point. The response looks fine right up until it doesn’t. The other field worth burning in: usage . It shows you how many tokens went in and came out. You want this number in your logs from day one not after you get a surprise bill. 🤯 3. Tokens I keep saying “24 input tokens.” Here’s what that means: Things that surprise people and is worth noting: Words don’t equal tokens. “Unbelievable” is one word but four tokens. The tokenizer splits on common substrings, not spaces. Code costs more than it looks def add(a, b): is 8 tokens. Every bracket and comma is its own token. JSON is expensive. {“a”:1} is 7 tokens. If your tool schemas are bloated, they’re quietly eating into your budget on every single request. Non-English costs more Japanese, Hindi, Arabic tend to run 2–4× the token count of the same content in English. If you’re building for a global audience, this changes your cost math a lot. Rule of thumb for English prose: ~1 token ≈ 4 characters ≈ 0.75 words. For everything else, run it through the tokenizer yourself before assuming. 4. The bill Two meters run on every call. They are priced differently Output tokens cost roughly 3–5× more than input tokens. That’s the one number to internalize about LLM pricing. cost = (input_tokens / 1,000,000) × input_price + (output_tokens / 1,000,000) × output_price Enter fullscreen mode Exit fullscreen mode Three things that follow from the asymmetry: Long prompts are cheap. Long responses are expensive. Stuffing 50 KB of context into a system prompt is fine. Asking for 50 KB of output is roughly 5× more expensive. “Thinking” tokens count as output. Reasoning models bill their internal thought at the output rate, even though you don’t see it. Tool schemas eat input on every call. They get resent with every request, just like the system prompt. At $0.006 per call, 100k calls a day is $600/month from one small feature. Add usage logging now, not when you get the alert. 🚨 5. The whole thing in 20 lines Here is the complete code of the API call we have discussed above: Jasmin2895 / TinyAgent No dependencies and no install setup it is just Node file with API key. Three things to try before the next post Run it and watch the numbers Make ten calls, change the prompt length, see how usage moves. You’ll build a real instinct for cost faster this way than reading any doc. Set max_tokens: 20 and ask for something long. Watch it cut off. Check stop_reason. This is a bug you’ll hit in production eventually better to meet it on purpose right now Build a multi-turn chat by hand. Keep a messages array, push each user message and each model reply onto it, and resend the whole thing every turn. Once you do this, you’ll immediately understand why long conversations get expensive you’re paying for the full history on every call. What’s next In the upcoming post series we will expand the ability of the TinyAgent to actually handle lot of things than just responding. Happy Coding! 👩💻
Hermes Mentor — A Local AI Agent That Gets You Out of Tutorial Hell
Hermes Agent Challenge Submission: Build With Hermes Agent This is a submission for the Hermes Agent Challenge : Build With Hermes Agent What I Built Every developer knows the feeling. You’ve watched 50 hours of YouTube tutorials. You “know” React. You “know” Python. Then you sit down to build something real — and you freeze. Not because you’re not smart. But because watching is not building . This is tutorial hell. I’ve lived it. I’ve watched juniors at work live it for months. Hermes Mentor is a fully local, privacy-first AI mentorship agent that pulls you out of it — not with more tutorials, but with a personalised project roadmap built from scanning your actual GitHub repos. Here’s what it does: 🔍 Audits your real GitHub repos — reads every public repo, checks languages, CI/CD configs, test files, README quality 🧠 Identifies your exact skill gaps — local LLM via Ollama reasons across your code to find what’s actually missing 🗺️ Generates a 4-week project roadmap — real projects, each one closing a specific gap, no tutorials 📬 Sends daily Telegram challenges — every weekday at 08:30, your nudge, hints if stuck, celebration when you ship 💬 Two-way Telegram agent — reply with your repo link and Hermes reads it, tracks your progress, creates TODO tasks 💾 Persistent memory — your developer profile lives in ~/.hermes/memory/ , updated every run 🔒 100% local and private — Ollama runs the LLM on your machine, nothing leaves your box Everything runs on WSL2. One command to start. Demo The audit running — 20 repos scanned in real time The roadmap printed — gaps identified, 4 weeks planned Telegram Message Hermes memory — your developer profile saved automatically Cron installed — daily nudges set up for 08:30 weekdays Daily nudge delivered — Week 1 Day 2 challenge on Telegram 🤯 The moment it became a two-way agent This is the screenshot that made me realise this project was something else entirely. I sent my GitHub repo link to the bot on Telegram after completing the CI/CD challenge. Hermes didn’t just reply with text. It: Read the GitHub repo link and understood the context Ran a terminal command — echo ‘Pipeline check is successful. Opening PR for review.’ Created a real Pull Request on GitHub — TheCoderAdi/Basic_Calculator · Pull Request #1 Marked the TODO as completed — “Create pull request for CI/CD pipeline changes.” → status: completed This is Hermes Agent’s tool use, terminal access, GitHub integration, and task planning all firing together in real time — over Telegram — powered entirely by a local LLM on my machine. No cloud. No API keys. A fully autonomous agent that reviewed my work, opened a PR, and closed its own task. All from a Telegram message. Landing Page thecoderadi.github.io Code Repository: github.com/TheCoderAdi/hermes-mentor Project Structure hermes-mentor/ ├── mentor_agent.py ← Core agent: GitHub audit + roadmap + Telegram ├── hermes_cron.py ← Daily nudge scheduler (weekdays 08:30) ├── setup.sh ← Automated setup wizard ├── requirements.txt ├── .env.example ├── hermes-mentor.html ← Project landing page ├── config/ │ └── hermes-config.yaml ← Hermes Agent config (Ollama + Telegram) └── skills/ └── github-audit-mentor.md ← Reusable Hermes skill file Enter fullscreen mode Exit fullscreen mode My Tech Stack Layer Tool Agent orchestration Hermes Agent (NousResearch) Local LLM Ollama — qwen2.5-coder:7b GitHub data PyGithub — GitHub REST API Messaging python-telegram-bot — Telegram Bot API Scheduling Hermes cron + Linux crontab Memory Hermes persistent memory — ~/.hermes/memory/ Skill system Hermes skill files — agentskills.io format Environment WSL2 on Windows Language Python 3.12 How I Used Hermes Agent Hermes Mentor doesn’t just mention Hermes — every core capability is actively used. Here’s exactly how: Persistent Memory — the agent remembers you forever After every GitHub audit, Hermes writes USER_TheCoderAdi.md directly into ~/.hermes/memory/ . Every future Hermes session loads this file automatically. The agent already knows your skill level, active roadmap week, and past struggles — without you ever re-explaining yourself. This is what turns a one-shot script into a real mentor. It builds a relationship with you over time. Skill Learning (GEPA Loop) — gets smarter every run The github-audit-mentor.md skill file is not static documentation. After every audit it gets updated with the latest findings — gaps found, roadmap generated, developer profile. This is Hermes’ Generate-Evaluate-Patch-Apply loop making the skill more accurate and personalised with each developer it touches. Cron Scheduling — autonomous daily action hermes_cron.py registers a weekday 08:30 cron job that auto-advances through your roadmap week and day, firing the right personalised Telegram nudge every morning. The user does nothing after setup. Hermes just shows up, every day, like a real mentor. Two-Way Telegram Gateway — live agent conversations The most unexpected moment in building this: when I started hermes gateway and sent my own GitHub repo link to the bot, Hermes read the message , recognised the URL, created TODO tasks with in_progress and pending status, and replied with next steps. That’s Hermes’ tool use, task planning, and messaging gateway all firing together in real time. Multi-Step Agentic Reasoning — the full loop Fetch repos → Extract language/CI/test signals → Reason about gaps → Generate targeted project per gap → Deliver via Telegram → Save to memory → Update skill file → Listen for replies → Track progress → Plan next steps Enter fullscreen mode Exit fullscreen mode Each step informs the next. This is what separates Hermes Mentor from a chatbot. Local LLM via Ollama — private by design The entire reasoning layer runs on your machine through Ollama. No OpenAI. No Anthropic. No billing. Your GitHub activity, your learning gaps, your daily habits — stay on your box. This felt philosophically aligned with Hermes itself — an open source agent you run on your own infrastructure. Why I Built This I’m an SDE at KFintech and I’ve been building things for years. But I remember tutorial hell clearly — watching videos for months, feeling productive, then sitting down to build something real and freezing completely. The problem isn’t knowledge. It’s the gap between watching and doing. Roadmap.sh gives you a static path. GitHub Copilot helps you write code. But nobody looks at what you’ve actually built and tells you specifically what to build next to close your specific gaps. That’s Hermes Mentor. And watching it scan my own GitHub, find my real gaps, generate a roadmap, send it to my Telegram, and then reply when I shared my repo — that moment reminded me why I love building things. Built by Aditya Swayam Siddha · @TheCoderAdi
Hack your AWS CLI to add CloudShell support and turn your terminal into a bastion
I’ve been using AWS CloudShell from the Console for a while. It’s convenient: a pre-authenticated shell in your browser, right there in the AWS Console. But I always wondered: why can’t I use it from my terminal? Why is there no aws cloudshell command? Turns out, you can make it happen. The API exists, it’s just not public. And once you have CLI access to CloudShell, you can do interesting things with it, like using a VPC-attached CloudShell as a bastion to reach your private RDS instances. Checkout the companion repository as you read through this blog post. CloudShell: an undocumented API AWS CloudShell has no official SDK or CLI support. But the Console has to talk to something , right? By looking at what the browser does when you open CloudShell, you can reverse-engineer the API. Thankfully, Jérémie Guyon already did that work and published a boto3-compatible service model. His work made this whole thing possible. The API is straightforward: create environments, start/stop them, create sessions, upload/download files. The session mechanism uses SSM’s WebSocket protocol under the hood, which means session-manager-plugin (the same binary that powers aws ssm start-session ) can connect to CloudShell sessions. Teaching the AWS CLI a new trick The AWS CLI has a little-known feature: aws configure add-model . Give it a JSON service model, and suddenly the CLI knows about a new service. AWS uses this internally for private previews. (The boto3 model from Jérémie’s repo just needs a “version”: “2.0” field added at the top level to become CLI-compatible.) Run: aws configure add-model \ –service-model file://cloudshell-cli-model.json \ –service-name cloudshell Enter fullscreen mode Exit fullscreen mode That’s it. Now I have aws cloudshell with tab completion and everything: $ aws cloudshell help AVAILABLE COMMANDS create-environment create-session delete-environment describe-environments get-environment-status start-environment stop-environment … Enter fullscreen mode Exit fullscreen mode Connecting to CloudShell from the terminal The workflow is simple: # Create or find an environment aws cloudshell create-environment –region eu-west-1 # Wait for it to be RUNNING aws cloudshell get-environment-status –environment-id
Reviving a 12K+ Star Abandoned Library: toastr-next v3 🍞
GitHub “Finish-Up-A-Thon” Challenge Submission This is a submission for the GitHub Finish-Up-A-Thon Challenge What I Built toastr-next v3 a complete revival of CodeSeven/toastr , one of the most-starred abandoned JavaScript libraries on GitHub with 12,000+ stars and no meaningful commits since 2016. toastr was the go-to notification library for millions of developers. But time wasn’t kind to it. it required jQuery, had no TypeScript, no dark mode, no accessibility, and its Gulp + LESS build chain was completely dead. It was a library everyone knew but nobody could use in a modern project without guilt. I picked it up, stripped it to the bones, and rebuilt it from scratch for 2026. From this 👇🏻: // 2015 — drag in jQuery just to show a toast < script src = " jquery.min.js " >< /script> / / 30 KB < script src = " toastr.min.js " >< /script> / / 5 KB // Total: ~87 KB of dead weight toastr . success ( ‘ Hello! ‘ ); Enter fullscreen mode Exit fullscreen mode To this 👇🏻: // 2026 — zero dependencies, full TypeScript, ~4 KB gzipped import { toastr } from ‘ toastr-next ‘ ; const toast = toastr . success ( ‘ Hello! ‘ ); await toast . dismissed ; // Promise API! Enter fullscreen mode Exit fullscreen mode ~4 KB gzipped. No jQuery. No bloat. Just toasts. 🍞 Demo Dark mode — toasts firing with progress bar Light mode — same demo, toggled with the ☀️ button 🌐 Live Demo: toastr-next.vercel.app/ 📦 npm: npmjs.com/package/toastr-next 🐙 GitHub: github.com/Divyesh-5981/toastr The Comeback Story Where it was The original repo — abandoned since 2016, 12k stars, zero recent activity Problem Detail jQuery required ~87 KB overhead just to show a notification No TypeScript No types, no IntelliSense, no safety No dark mode Hard-coded colors, no CSS variables No accessibility Screen readers couldn’t detect toasts at all JS-driven animations Layout thrash, janky on low-end devices No Promise API No way to await a toast dismissal No keyboard support Couldn’t dismiss with Escape key Dead build toolchain Gulp + LESS, completely unmaintained since 2016 No ESM support Global UMD only, no tree-shaking What I built 🏗 TypeScript Rewrite (Zero Dependencies) No jQuery: 100% strict TypeScript with full JSDoc. Universal Formats: Ships in ESM, CJS, UMD, and IIFE (works from Vite to raw