# Harshith Nayaka L — AI Engineer, Full-Stack (full content) > Full-stack AI Engineer based in Bengaluru (Bangalore), India, building AI agents, RAG pipelines, LLM applications, and the production backends and interfaces around them. Currently AI Workflow Engineer at DemandNXT. Available for select freelance projects. Harshith Nayaka L specialises in the unglamorous part of applied AI — making systems behave the same way on the hundredth run as on the first. Core areas: AI agents and orchestration, retrieval-augmented generation (RAG), LLM integration, multi-modal pipelines (voice, vision, text), output reliability via schema-constrained generation, full-stack AI applications, backend engineering (FastAPI, Node, Postgres), local and cloud inference (Google Cloud AI, Hugging Face, Ollama), and workflow automation with n8n. Building with code since 2022; reached the semi-finals of the Gen AI Exchange Hackathon 2025 (a national event with 270,000+ developers, organised by Google Cloud). This is the expanded companion to /llms.txt: the complete text of every case study on the site, inlined so it can be read in one fetch without crawling each page or executing JavaScript. ## CraftConnect — Gen AI Exchange Hackathon 2025 URL: https://harshith-nayaka-l-portfolio.vercel.app/work/craftconnect Markdown: https://harshith-nayaka-l-portfolio.vercel.app/work/craftconnect/index.md **Outcome:** Let an artisan stand up an online storefront by talking and showing a product, instead of typing forms in a language that isn't theirs. - **Role:** Product lead, full frontend, co-built backend - **Context:** Gen AI Exchange Hackathon 2025 - **Scale:** 270,000+ developers nationally - **Status:** Built & deployed on Google Cloud ### The problem Most artisans don't lose sales because their craft isn't good enough. They lose them because getting a product online means typing English product descriptions, setting prices, writing tags, and fighting a form-heavy interface that assumes you already know e-commerce. That barrier is the whole problem. The skill is in the hands; the friction is in the keyboard. ### What I built CraftConnect removes the keyboard. An artisan photographs a product and describes it out loud in their own language. From that, the system produces a complete, structured listing: a title, a written description, suggested categories and tags, and a storefront page, ready to publish. It's multi-modal by necessity, not for show. The photo carries information the voice doesn't (material, colour, form), and the voice carries information the photo can't (story, intended use, price intent). The pipeline fuses both into one validated listing. I led the product, built the entire frontend, co-built the backend, and owned the decisions that actually mattered: which AI services to use where, and what the end-to-end user journey should feel like. ### Pipeline - **Capture:** Voice input (Artisan's own language) → Product photo (One or more images) - **Understand:** Speech-to-text (Transcribe + detect language) → Vision analysis (Gemini reads the product) - **Compose:** Fuse signals (Voice intent + visual attributes) → Listing generation (Gemini, schema-constrained) - **Verify:** Validate fields (Title, price, tags present & sane) → Localize (Buyer-facing language) - **Publish:** Storefront page (Ready to go live) ### The judgment calls **Voice and vision are treated as two witnesses, not one** The photo and the spoken description are analyzed separately, then reconciled. When they agree, confidence is high. When they conflict (the voice says 'silk' but the image reads cotton), the system surfaces it rather than silently guessing. That's a deliberate reliability choice, not an accident of the model. **Generation is schema-constrained, not free-text** The model isn't asked to 'write a listing'. It's asked to fill a defined structure: title, description, category, tags, price band. Constraining the output shape is what makes it usable by the rest of the app instead of being a paragraph someone still has to parse. **Language is decoupled from logic** The artisan speaks one language; the buyer may read another. Localization happens as its own step at the end, so the understanding pipeline never has to care what language it started in. One pipeline, many markets. **AI service selection was a judgment call, not a default** I chose where to spend model capability and where a cheaper, narrower service was enough. Speech-to-text, multimodal reasoning, and generation are different jobs with different cost and latency profiles, and they were wired accordingly. ### What it changed **What changed:** A task that previously required literacy in English, e-commerce, and form-filling collapses into 'show it and say it'. The artisan's expertise stays in the craft, not the keyboard. **Recognition:** Built and deployed for the Gen AI Exchange Hackathon 2025, a national event with 270,000+ developers, and reached the semi-finals. **Honest scope:** This was a hackathon build, not a production marketplace. The value it proves is the interaction model and the multi-modal pipeline behind it, both of which hold up. ### Built with Gemini (multimodal), Google Cloud Speech-to-Text, Google Cloud, React, Node.js, Structured / schema-constrained output ### Links - [View on GitHub](https://github.com/HarshithNayakaL/craftconnect) --- ## Creative-Ops Pipeline — Flagship case study URL: https://harshith-nayaka-l-portfolio.vercel.app/work/creative-ops-pipeline Markdown: https://harshith-nayaka-l-portfolio.vercel.app/work/creative-ops-pipeline/index.md **Outcome:** A multi-model content pipeline that turns a one-line brief into validated, on-brand output, without a human babysitting every step. - **Type:** Production AI pipeline - **Focus:** Reliability & cost engineering - **Pattern:** Multi-model + QA gates ### The problem Producing on-brand content at volume is mostly invisible manual labour: drafting, reformatting, checking it didn't drift off-brand, fixing the one field that came back malformed, doing it again tomorrow. It scales linearly with headcount, which is to say it doesn't scale. The interesting problem isn't 'can an LLM write this'. It's 'can a system produce this reliably, at a sane cost, and fail safely when a model misbehaves'. ### What I built A pipeline that takes a structured brief and runs it through tiered models, schema-constrained generation, and explicit quality gates before anything is considered done. Cheap models do the bulk work; expensive models are spent only where judgment is actually needed. Every stage assumes the model can be wrong. Output is validated against a schema, checked by a QA gate, and when something fails the run is logged with enough context to recover, not silently dropped. This is a clean rebuild around public APIs that demonstrates the architecture and the engineering judgment behind it, with generic demo content in place of any real campaign data. ### Pipeline - **Intake:** Structured brief (What, for whom, constraints) - **Route:** Draft (low-cost model) (Bulk generation) → Refine (high-capability model) (Only where it pays off) - **Structure:** Schema-constrained output (Generate then validate) - **Gate:** Rule checks (Format, fields, limits) → LLM critique gate (On-brand? On-spec?) - **Resolve:** Error log that still saves (Recover, don't drop) → Approved output (Ready downstream) ### The judgment calls **Cost-tiered models, spent on purpose** Not every token needs a frontier model. The bulk of generation runs on a cheaper model; the expensive one is reserved for the steps where its judgment changes the outcome. The result is the same quality bar at a fraction of the bill. **Generate, then validate, then trust** Structured output is requested against a schema, but the schema request is treated as a hope, not a guarantee. Every output is validated before the pipeline acts on it. Malformed responses are caught at the boundary, not three steps later. **QA gates as code, not vibes** Quality is checked explicitly: deterministic rule checks for the things rules can catch, and an LLM critique pass for the judgment calls ('is this actually on-brand'). Nothing passes on optimism. **Failures save their work** When a run breaks, it isn't thrown away. It's logged with enough context to resume or retry the failing step, so a single bad model response never costs the whole job. ### What it changed **What it demonstrates:** Judgment about where to spend compute, how to make LLM output trustworthy enough to build on, and how to fail without losing work. **Honesty note:** This is a clean rebuild on public APIs with generic demo content. No client data, no proprietary logic. The skill is the point, not the source material. ### Built with LLM orchestration, Tiered model routing, Schema-constrained output, Validation layer, QA gates, Structured logging --- ## Maestro — Multi-model orchestration URL: https://harshith-nayaka-l-portfolio.vercel.app/work/maestro Markdown: https://harshith-nayaka-l-portfolio.vercel.app/work/maestro/index.md **Outcome:** Get frontier-quality answers out of free models by orchestrating them: a conductor routes one task across thinker, worker, and verifier roles, and shows its work at every step. - **Type:** Open-source orchestration engine - **Pattern:** Conductor + Thinker / Worker / Verifier - **Stack:** Python + FastAPI, deployed - **Status:** Live & deployed ### The problem A single free model is uneven: strong on some tasks, unreliable on others, and impossible to fully trust because you can't see how it reached an answer. The obvious workaround, pay for a bigger model, isn't the interesting one. The interesting claim, backed by Sakana's TRINITY and Mixture-of-Agents research, is that intelligent orchestration beats raw model size. Maestro is a glass-box, open-source rebuild of that idea: not cheaper tokens (the models are already free), but better answers from them, with the reasoning made visible instead of hidden. ### What I built A Conductor model reads a task and assigns Thinker, Worker, and Verifier roles across a pool of free LLMs, then a Synthesizer produces the final answer. Crucially, the Verifier is always a different model family than the Worker, which mitigates the well-documented 10–25% self-preference bias in LLM-as-judge. Every step appends to a structured, replayable decision-log, the plan, the routing rationale, each model's output, the verifier's verdict, token and latency cost. That log is the actual product: it's what a black-box orchestration layer can't give you. It's engineered to run in the real world on free tiers: a per-model token-bucket limiter enforcing both RPM and TPM, exponential backoff with jitter on 429s, and diversified fallback chains so one failed call never crashes a run. Models are swapped by editing one config file, never the orchestration code. It deploys publicly without leaking your quota: API-key auth, per-client rate limiting (globally consistent via Upstash Redis on serverless), security headers, input hardening, and strict CORS, with a startup self-audit that warns on unsafe production config. ### Pipeline - **Task:** Task in (A single prompt / problem) - **Conduct:** Conductor (Plan + routing rationale) - **Reason:** Thinker (Strategy for the answer) → Worker (Produces the answer) - **Verify:** Verifier (Different model family; 1 bounded retry) - **Deliver:** Synthesizer (Final answer) → Decision-log (Every step, replayable) ### The judgment calls **The judge is never the same family as the worker** LLM-as-judge has a measured 10–25% self-preference bias, so a model grading its own family's output is compromised. Maestro's verifier is always a different model family than the worker, and a failed check triggers exactly one bounded retry rather than an open-ended loop. **The decision-log is the product** Every run emits a complete JSON log: the plan, why each model was routed where, each step's output and verdict, and the token/latency totals. You can replay and audit exactly how an answer was built, which is the whole point of a glass-box design. **Built for free-tier limits, honestly** Groq's free tier binds on tokens-per-minute, not requests. Maestro reserves estimated tokens before a call so it defers instead of getting 429'd, backs off with jitter when it does, and falls back across model families. Long-context steps route to Gemini's roomier budget. **Swap models without touching code** The model pool, role chains, and rate limits live in one config file; the orchestration logic never hard-codes a model ID. When the Groq catalog rotates, or a Llama model is retired, you edit config, not Python. **Deployable without leaking your quota** API-key auth, per-client rate limiting that stays consistent across serverless instances via Upstash Redis, security headers, input hardening, and a production self-audit that warns on wildcard CORS or mock mode left on. It's meant to be put on the public internet safely. ### What it changed **What it demonstrates:** Systems thinking about LLMs: routing, verification across model families, honest cost accounting, rate-limit engineering, and a security posture, all in service of making unreliable free models produce trustworthy, auditable output. **Honest disclosure:** Maestro's conductor is prompt/rule-based, not a trained coordinator like Fugu's evolved model. It's a faithful re-creation of the concept, made open and transparent, not a claim to have reproduced the trained artifact. The benchmark harness is deliberately built to report where orchestration doesn't help. **Status:** Open-source (MIT) and deployed live on Vercel, with a mock provider so the full flow, dashboard, and tests run offline with no API keys. ### Built with Python, FastAPI, Groq (Llama / Qwen / gpt-oss), Google Gemini, Pydantic, Token-bucket rate limiting, Upstash Redis, Vercel / Railway, n8n ### Links - [Live app](https://maestro-psi-neon.vercel.app/) - [View on GitHub](https://github.com/HarshithNayakaL/Maestro) --- ## Cannon — Multi-agent, not multi-task URL: https://harshith-nayaka-l-portfolio.vercel.app/work/cannon Markdown: https://harshith-nayaka-l-portfolio.vercel.app/work/cannon/index.md **Outcome:** A personal assistant used daily, not demoed once: independent domain specialists that share infrastructure but deliberately never share context, with isolation enforced at the query rather than left to convention. - **Type:** Multi-agent assistant, deployed - **Pattern:** Domain specialisation, not task orchestration - **Stack:** Next.js 16 + Vercel AI SDK 7 - **Tests:** 91 unit + 13 e2e specs ### The problem A single generalist chatbot wearing every hat is the easy build and the wrong one. Ask it about a workout and a work deadline in the same thread and it's carrying both contexts at once, with no real boundary between them, and no persona suited to either. The harder, more honest problem: build something used every day, not shown once. A fitness log with real training data behind it, a work board with real tasks on it, judged by whether it survives daily use, not by how it looks in a five-minute demo. ### What I built Cannon runs independent domain agents, each with its own persona, its own system prompt, its own tools, and its own retrieval scope. A fitness agent and a work agent share infrastructure but never share context. Cannon's own README draws the contrast directly: its sibling project Maestro splits a single task across collaborating roles; Cannon runs independent experts that don't collaborate at all. Different architecture, different problem. Isolation is enforced at the query, not by convention. The fitness agent can't read work documents even by accident: retrieval takes the agent id as an argument and the Postgres function filters on it internally, so there's no post-filter step a caller could forget to add. The memory store enforces the same boundary as a WHERE clause on the scan. Both paths are unit-tested with mirror-image queries. Provider fallback is modeled as a real interface, not a try/catch. A fallback model implements the AI SDK's provider interface and wraps Groq behind Gemini, so streaming, tool execution, and the UI stream protocol are all unaware a swap ever happened. It distinguishes two real failure modes — a call that never connects, versus one that connects and then errors mid-stream — and once real content is flowing, a failure is surfaced rather than silently restarted, because re-running a partially executed tool chain is worse than a visible error. Every external dependency has a working fallback, all the way down: no provider key and no database, and the app still boots, builds, passes its full test suite, and serves a working demo. Inference falls back to a scripted model implementing the real provider interface; embeddings fall back to a deterministic lexical embedder; storage falls back to a process-local store. That's what lets CI run true end-to-end tests with zero secrets, exercising the production code path instead of a mocked-out shortcut. ### Pipeline - **Route:** Agent registry (Explicit tab routing) - **Specialise:** Fitness agent (3 tools + RAG) → Work agent (4 tools + RAG) - **Isolate:** Query-scoped retrieval (Agent id filters at the DB) - **Infer:** Groq (primary) (Time-to-first-token budget) → Gemini (fallback) (Different vendor, uncorrelated outage) - **Answer:** Typed tool-result cards (Not pasted JSON) ### The judgment calls **Isolation is a database filter, not a habit** The fitness agent structurally cannot see work data. Retrieval takes the agent id as an argument the Postgres function filters on internally, and the memory store applies the same boundary as a WHERE clause — there's no separate post-filter step a caller could forget, and both paths carry mirror-image unit tests. **A fallback chain that's a typed model, not a caught exception** The fallback wraps Groq behind Gemini by implementing the AI SDK's actual provider interface, so every layer above it — streaming, tool execution, the UI stream protocol — stays unaware a swap happened. It separates a call that never connects from one that connects and fails mid-stream, and treats a mid-stream failure as something to surface, not silently retry, since re-running a half-executed tool chain is its own hazard. **Fallbacks all the way down, so CI tests the real path** No API key, no database, and the app still boots, builds, and serves a working demo: a scripted model standing in for inference, a deterministic lexical embedder standing in for embeddings, a process-local store standing in for Postgres. That's what makes it possible for CI to run true end-to-end tests with zero secrets against the production code path, not a mocked shortcut. **The Prompt Inspector makes the engineering legible** A docked panel shows the live system prompt driving the active agent, the tool signatures in scope, which provider actually answered, whether a fallback fired, and the retrieved chunks with their similarity scores. A real debugging aid that doubles as evidence for anyone reading the repo. **No login, and that's a decision, not an oversight** This is a single-user tool run by one person for their own training log and task board — a login screen would be friction with nothing behind it. Groq and Gemini already enforce their own free-tier ceilings, and the fallback chain already treats a 429 from either as a normal handoff, so there's no second rate limiter layered on top. The README states the tradeoff plainly: anyone with the URL can use the deployment, acceptable for a personal tool with an unlisted link, not something to hand to a wider audience without adding a gate back. ### What it changed **Tested, not just built:** 91 unit tests cover the tools, the training maths, RAG scoping, chunking, all four failure modes of the fallback chain, and the store's filters and ranking. 13 end-to-end specs run twice each, desktop and mobile, against a real production build with zero provider keys — both agents' full tool loops, transcript isolation, the inspector, the responsive swap, theme persistence, and horizontal overflow at five breakpoints from 375px to 2200px. **Deploys with nothing to configure:** Import the repo into Vercel with no environment variables set and it deploys and works immediately, seeded with fitness and work data. Real inference, real embeddings, and durable storage are opt-in upgrades layered on top of a system that already works without them. **Honest about its own limits:** The README states its known limits directly rather than hiding them: no login (deliberate, for a single-user tool with an unlisted URL), no rate limiting beyond what Groq/Gemini already enforce, and no auto-routing between agents in v1, because a classifier that guesses wrong at 11pm is worse than an explicit tab. ### Built with Next.js 16 (App Router), Vercel AI SDK 7, Groq + Gemini, Supabase Postgres + pgvector, TypeScript (strict), Vitest + Playwright, GitHub Actions ### Links - [Live app](https://cannon-multi-agents.vercel.app) - [View on GitHub](https://github.com/HarshithNayakaL/cannon-multi-agents) --- ## ReplyDesk — WhatsApp lead agent URL: https://harshith-nayaka-l-portfolio.vercel.app/work/replydesk Markdown: https://harshith-nayaka-l-portfolio.vercel.app/work/replydesk/index.md **Outcome:** Answer every inbound WhatsApp lead in seconds, automatically, with a live operations dashboard for the lead feed, pipeline, and response times. - **Type:** Lead-response agent + dashboard - **Focus:** Speed-to-lead - **Role:** Solo build - **Status:** Interactive prototype ### The problem 78% of customers buy from the business that responds first, yet the average small business takes around 29 hours to reply to a lead. By then the customer has already bought from someone faster. The gap isn't intent, it's operations. Leads arrive on WhatsApp at all hours, a human can't sit on the inbox 24/7, and every minute of delay is measurable lost revenue. ### What I built ReplyDesk is a WhatsApp lead agent that captures every inbound message and fires a first reply in roughly eight seconds, so no lead sits waiting. Around it sits a live operations dashboard, the piece that makes the automation legible to the person running it. The dashboard is the control room: a real-time lead feed, a pipeline breakdown of where each lead sits, a 'needs your attention' queue that surfaces the ones a human should actually touch, and response-time KPIs that keep the whole thing honest against that first-responder metric. It's a self-contained, interactive front-end prototype, you can simulate an incoming lead and watch it flow through capture, reply, and pipeline in real time, built to prove the interaction model and the operational value, not to be a finished SaaS. ### Pipeline - **Inbound:** WhatsApp message (A new lead arrives) - **Capture:** Into the lead feed (Logged in real time) - **Respond:** Agent first reply (~8s, every time) - **Qualify:** Score & route (Hot / warm / needs attention) - **Operate:** Live dashboard (Feed, pipeline, KPIs) ### The judgment calls **Speed is the entire product** The whole system is organized around one number: time to first reply. Answering in seconds instead of hours is the difference between winning the lead and losing it, so that metric sits front and center on the dashboard. **A dashboard that shows the pipeline, not just a chat log** Leads are broken down by stage so the operator can see the shape of the funnel at a glance, how many are hot, how many are cooling, where things are stuck, rather than scrolling an undifferentiated inbox. **Automation that still asks for a human when it matters** A 'needs your attention' queue pulls out the leads that genuinely require a person, so automation handles the volume while the operator spends their time only where it moves the needle. **Built to be felt, not just described** The prototype lets you simulate an incoming lead and watch it move through the system live, so the speed-to-lead value is something you experience in the interface rather than a claim in a slide. ### What it changed **What it proves:** Product sense around a real, quantified business problem: turning the 'respond first' statistic into an operational tool, and designing the dashboard an operator would actually run their day from. **Honest scope:** This is a self-contained interactive front-end prototype with a simulate-lead demo, not a WhatsApp-connected production deployment. It's proof of the interaction and operations model, and it's honest about being exactly that. ### Built with WhatsApp (lead channel), LLM agent, Real-time dashboard, Vanilla JavaScript, HTML / CSS ### Links - [View on GitHub](https://github.com/HarshithNayakaL/Whatsapp-Agent-Dashboard) --- ## Nova — Cost-tiered model routing URL: https://harshith-nayaka-l-portfolio.vercel.app/work/nova-ai Markdown: https://harshith-nayaka-l-portfolio.vercel.app/work/nova-ai/index.md **Outcome:** A chat app that reads every turn, scores how hard it is, and sends it to the smallest model that can carry it — stamping the lane, the reasoning and the cost onto every answer. - **Type:** Deployed web app - **Roster:** gpt-oss-20b · qwen3.6-27b · gpt-oss-120b - **Routing:** Signal scoring + arbiter model - **Role:** Solo build ### The problem Every chat app sends one model everything. "Translate this line" and "find the race condition in this worker pool and prove it" land on the same endpoint, so you either overpay on the easy turns or underserve the hard ones. There is no third option when the roster is one model long. The obvious fix — a router that starts cheap and climbs — is the trap. Score from zero upward and almost every turn clears the bar for the fast lane, because most prompts trip two or three keywords at most. You end up with a router that technically works and quietly answers everything badly. ### What I built Nova scores each turn on four axes — reasoning, code, breadth and context — with deterministic keyword and pattern evidence that runs instantly and always runs. That produces a prior: a lane and a complexity reading between 0 and 100. Then a small arbiter model reads the same turn alongside the prior and returns a lane, a complexity number and a one-line rationale as JSON. It is allowed to overrule the arithmetic, because a scorer cannot tell that "explain CRDTs to someone who ships code, not papers" is a drafting job rather than a research one. Three lanes: gpt-oss-20b for lookups and rewrites, qwen3.6-27b for drafting and long context, gpt-oss-120b for reasoning, math and code review. Every answer is stamped with the lane it took, the complexity reading, the arbiter's rationale and what it cost, and the lane switch overrides routing entirely when you already know what you want. ### Pipeline - **Read:** Turn + history (What the user just sent) - **Score:** Four axes (reasoning · code · breadth · context) → Complexity prior (0-100, deterministic) - **Arbitrate:** Arbiter (gpt-oss-20b, JSON, 4s budget) → Floor rules (Pushback never routes below L2) - **Answer:** Lane model (20B, 27B or 120B) - **Show:** Stamped answer (Lane, complexity, rationale, cost) ### The judgment calls **The middle lane is the default, not the cheap one** A turn has to earn its way down to the fast lane by being demonstrably trivial, or up to the deep lane by being demonstrably hard. Climbing from zero would leave everything in the 20B model, which is the failure mode that makes routers feel worse than no router at all. Starting from the middle means the router only has to recognise the two extremes, which is the part it can actually do reliably. **The arbiter is allowed to be slow, wrong or absent** It gets a 4-second budget and a 120-token cap. If it times out, returns unparseable JSON, or names a lane that does not exist, the deterministic prior stands. If the routing endpoint itself fails, the browser falls back to its own copy of the scorer. Routing never blocks an answer — the worst case is a blunter decision, never a spinner. **Keyword evidence has diminishing returns** Each axis scores as 100 × (1 − decay^hits), so the first match already counts for most of the signal and the fifth adds almost nothing. A flat per-hit score reads real prompts as trivial, because people write two clauses, not a checklist of trigger words. **Disagreement is a routing signal** "That's wrong", "go deeper", "not what I asked" — a turn that pushes back on the previous answer never routes below the middle lane, whatever the keywords say. The last answer was already judged insufficient; sending the follow-up somewhere smaller is the one move guaranteed to be wrong. **One coordinator changes the answer** "Summarise this" is trivial. "Summarise this and list the open questions" is two asks wearing the same opener, so the presence of a coordinator disqualifies a turn from the trivial shortcut. Cheap tests like this catch the cases where a scorer would otherwise be confidently wrong. **Routing costs about as much as a greeting** The arbiter runs on the cheapest lane, capped at 120 tokens with temperature zero and JSON-mode enforced. Paying a 120B model to decide which model to use would defeat the entire exercise. ### What it changed **What changed:** Difficulty stopped being something the user has to declare. There is no model picker to get wrong, and no flat rate for turns that did not need it — but the decision is never hidden, because every answer shows the lane, the reading and the rationale that produced it. **What it took to be safe:** Three levels of fallback for one decision that is not allowed to fail: arbiter to prior, endpoint to browser-side scorer, automatic routing to a manual lane switch. The provider key stays in the serverless function throughout; the browser never holds a credential, and threads live in localStorage rather than on a server. **Honest scope:** A deployed personal app, not a production service. There is no evaluation set proving the routing beats always-using-the-large-model, and the lane assignments are judgement rather than measurement. The architecture is the claim here, not a benchmark. ### Built with gpt-oss-20b / 120b, qwen3.6-27b, Groq · Hugging Face routers, Vercel serverless, Streaming, Vanilla JS, no framework ### Links - [Live app](https://custom-gpt-silk.vercel.app/) - [View on GitHub](https://github.com/HarshithNayakaL/CUSTOM-GPT) --- ## BlogSpace — Live & deployed full-stack URL: https://harshith-nayaka-l-portfolio.vercel.app/work/blogspace Markdown: https://harshith-nayaka-l-portfolio.vercel.app/work/blogspace/index.md **Outcome:** A complete blogging platform with authentication, roles, and an admin panel, built end to end and running in production. - **Type:** Full-stack web app - **Stack:** React + Node + MongoDB - **Role:** Solo, front to back ### The problem A blogging platform sounds simple until you list what it actually needs: accounts, secure login, two kinds of users, content that only the right people can edit, and an admin who can moderate all of it. That's a real application, not a toy. I built it solo, end to end, as proof I can take a full-stack product from auth to deployment without hand-waving the hard parts. ### What I built BlogSpace handles the full lifecycle: register and log in with JWT-based auth and hashed passwords, write and publish posts in a rich-text editor, and manage everything through an admin panel with role-based access. Users get drafts, published states, tags, auto-generated excerpts and read-time. Admins get user promotion/demotion and post moderation. It's deployed across Netlify, Render, and MongoDB Atlas, and it's live, not a localhost screenshot. ### Pipeline - **Auth:** Register / login (JWT + bcrypt) → Role assignment (User vs admin) - **Content:** Rich-text editor (Draft / publish) → Post CRUD (Owned by author) - **Guard:** Protected routes (Access by role) - **Admin:** Moderation panel (Manage users & posts) ### The judgment calls **Auth done properly, not faked** JWT tokens, bcrypt-hashed passwords, protected routes, and role-based access control. The boring security fundamentals that separate a real app from a tutorial, implemented rather than skipped. **Two user classes, enforced server-side** Users manage their own content; admins manage everyone's. The boundary is enforced on the backend, not hidden in the UI, so the permission model actually holds. **Deployed across three services** Frontend on Netlify, backend on Render, database on MongoDB Atlas. Wiring those together and keeping them talking in production is its own skill, and it's live. ### What it changed **What it proves:** End-to-end full-stack capability: a working auth system, a real permission model, and a deployment that strangers can actually use. **Try it:** It's live and clickable, with demo accounts available, the strongest kind of proof: not a description, a working thing. ### Built with React, Node.js / Express, MongoDB / Mongoose, JWT + bcrypt, Netlify + Render ### Links - [View on GitHub](https://github.com/HarshithNayakaL/blogspace-internship) --- ## AI Notes — Local inference, no server URL: https://harshith-nayaka-l-portfolio.vercel.app/work/ai-notes Markdown: https://harshith-nayaka-l-portfolio.vercel.app/work/ai-notes/index.md **Outcome:** A note-taking app whose AI features run entirely on your own machine, so your notes never leave it. - **Type:** Local-LLM web app - **Model:** DeepSeek R1 via Ollama - **Role:** Solo build ### The problem Almost every AI note app sends your writing to a third-party API. For private notes, that's the opposite of what you want, and it means the app is useless offline. I wanted to show the alternative most people skip: useful AI features that don't require a cloud call, a subscription, or trusting someone else with your data. ### What I built AI Notes runs DeepSeek R1 locally through Ollama, called straight from the browser. Summarize, improve writing, expand an idea, generate questions, extract keywords, all of it happens on the user's own machine. Built with plain HTML, CSS, and JavaScript, no frameworks, with notes stored locally in the browser. Once the model is pulled, the whole thing works offline. The point was to keep it dependency-light and prove local inference is a real option, not a compromise. ### Pipeline - **Write:** Note in browser (Stored locally) - **Local model:** Ollama runtime (On the user's machine) → DeepSeek R1 (No external call) - **Assist:** Summarize / expand / extract (AI actions) - **Stay private:** Nothing leaves the device (Offline-capable) ### The judgment calls **Inference stays on the machine** The model runs through Ollama locally. There's no API endpoint receiving your notes, because there's no external call at all. Privacy isn't a policy promise here, it's the architecture. **Works offline once set up** Pull the model once and the app keeps working with no internet. The AI features don't depend on a server being up or a bill being paid. **Deliberately dependency-light** Plain HTML, CSS, and JavaScript, no framework, browser storage for the notes. Small enough to understand fully, which was the point. ### What it changed **Published research:** The approach behind this project was written up and published: "AI-Powered Note-Taking System: A Local Machine Learning Approach DeepSeek R1 Integration", International Journal of Research Trends and Multidisciplinary Research (IJRTMR), Nov-Dec 2025, pp. 178-189 (DOI 10.59256/ijrtmr.20250506023). The paper reports 87% user satisfaction on content summarization with response times of 1.9-3.8 seconds. **What it demonstrates:** That local and self-hosted inference is a practical choice, and the awareness of when keeping data on-device matters more than convenience. **Scope:** A focused solo build proving the local-LLM pattern, not a feature-complete notes product. ### Built with DeepSeek R1, Ollama (local), Vanilla JavaScript, Browser storage ### Links - [View on GitHub](https://github.com/HarshithNayakaL/AI-Notes-App) - [Read the paper (DOI)](https://www.doi.org/10.59256/ijrtmr.20250506023) --- ## Contact Site: https://harshith-nayaka-l-portfolio.vercel.app/ Email: harshith28124@gmail.com GitHub: https://github.com/HarshithNayakaL LinkedIn: https://www.linkedin.com/in/harshithnayakal