# Projects

## Wingmate

An open-source, local-first outbound agent you drive by talking to a coding agent. It finds a prospect signal, verifies it, maps it to your own project ledger, and drafts outreach through a template that encodes your voice, never its own.

Project URL: https://wingmate.krishb.tech/
GitHub: https://github.com/KrishBakshi/wingmate

Technologies: Python, Terminal, Google Cloud Platform, Cursor, Claude Code, Codex, Agent Skills

## Overview

**Wingmate** is a research-led outbound agent with an unusual constraint: it never
lets a language model write the sentence.

```
find prospects → verify signals → identify credible fit → draft in your voice → review and send
```

There is no model API key anywhere in the repo. Rendering is deterministic
placeholder substitution. The intelligence is whichever coding agent you already
have open, Claude Code, Codex, Cursor, or a human typing the CLI directly,
reading a plain instruction file (`AGENTS.md`) and a set of skills. Wingmate
supplies the parts that keep that agent honest: a ledger it cannot invent facts
around, and a template it cannot paraphrase out from under you.

## The Problem It Actually Solves

Cold outreach written by an LLM fails in two specific, predictable ways: it
invents things about you, and it sounds like it was written by an LLM. Both
failures come from the same design mistake, letting the model author the
message. Wingmate's whole architecture is one move: split *facts*, *wording*,
and *judgement* into three places that only one of them, the agent, is allowed
to connect.

| Layer | Owns | Lives in |
|---|---|---|
| Facts | your projects, metrics, links | `data/identity.yaml` |
| Wording | the actual sentences | `templates/*.yaml` |
| Judgement | which template, which project, what to say | the agent, reading `AGENTS.md` |

The agent substitutes declared placeholders into a template it did not write,
using facts it did not invent. What is left for it to decide is *which*
template and *which* project entry are the right fit for this prospect, which
is exactly the kind of judgement a template can't encode and a static rule
can't automate either.

## How a Draft Actually Gets Made

A request like *"draft an intro to the founder of Acme about their inference
work"* runs through one path every time, whether it's typed by a person or an
agent acting on their behalf. The two checkpoints on the right are not
suggestions the agent can skip. They sit inside `GmailService` itself, so even
an inline script that imports the service directly still has to clear them.

[[graph]]
{
  "nodes": [
    { "id": "templates", "label": "templates/*.yaml", "row": 0, "col": 1, "note": "fixed wording" },
    { "id": "identity", "label": "identity.yaml", "row": 0, "col": 2, "note": "public section only" },
    { "id": "request", "label": "NL request", "row": 1, "col": 0 },
    { "id": "select", "label": "Select template", "row": 1, "col": 1, "note": "channel + relevance" },
    { "id": "render", "label": "Render", "row": 1, "col": 2, "note": "placeholder substitution" },
    { "id": "tgate", "label": "Template gate", "row": 1, "col": 3, "note": "guard_send" },
    { "id": "dgate", "label": "Disclosure gate", "row": 1, "col": 4, "note": "guard_disclosure" },
    { "id": "draft", "label": "Gmail draft", "row": 1, "col": 5, "note": "send is opt-in" },
    { "id": "ask1", "label": "Ask user: still draft it?", "row": 2, "col": 3, "isolated": true },
    { "id": "ask2", "label": "Ask user: still send it?", "row": 2, "col": 4, "isolated": true }
  ],
  "edges": [
    { "from": "request", "to": "select" },
    { "from": "templates", "to": "select", "kind": "dashed", "label": "channel prefix" },
    { "from": "select", "to": "render" },
    { "from": "templates", "to": "render", "kind": "dashed", "label": "fixed wording" },
    { "from": "identity", "to": "render", "label": "facts" },
    { "from": "render", "to": "tgate" },
    { "from": "tgate", "to": "dgate", "label": "score ≥ 0.75" },
    { "from": "tgate", "to": "ask1", "kind": "dotted", "label": "score < 0.75" },
    { "from": "dgate", "to": "draft", "label": "clean" },
    { "from": "dgate", "to": "ask2", "kind": "dotted", "label": "private data hit" }
  ],
  "legend": [
    { "kind": "solid", "label": "normal path" },
    { "kind": "dashed", "label": "static input" },
    { "kind": "dotted", "label": "blocked, needs a yes" }
  ],
  "caption": "Every render passes both gates before a Gmail write, whether the caller is the CLI or an inline script."
}
[[/graph]]

**The template gate** does not check the message is *true*, it checks the message
is *this template*. `src/services/template_matcher.py` strips HTML and
whitespace from the body, then measures how much of the template's fixed
wording, everything outside `{placeholders}`, survives inside it, using a
`SequenceMatcher` recall score with a minimum block size so stray short matches
can't accumulate a pass. A rendered template scores `1.0`. Free-form prose
written from scratch usually lands under `0.5`. Below the threshold (`0.75` by
default) nothing goes out; the service returns a `requires_confirmation`
payload with the exact question to relay to the user, verbatim, and only a
`yes` unlocks `--allow-unmatched`.

**The disclosure gate** sits between retrieval and generation for a different
reason: `identity.yaml` has a `public` section outbound is built from and a
`private` section it never is. `load_private()` refuses without an explicit
override and withholds the *values* even while refusing, it hands back only the
field names, so the agent knows what's being asked for without ever holding the
secret. The gate then re-checks the *finished body* too, because a private
value that arrived some other way, typed from memory, copied from an old
thread, still has to be caught before it reaches Gmail. It fires on exact
values from `private` and on pattern rules, phone numbers, CTC figures, notice
periods, government IDs, that hold even with an empty `private` section, so a
fresh clone with no data on disk still fails closed.

Both gates are code, not agent discipline. What is still agent discipline is
everything upstream of them, picking the right template, mapping the right
project by relevance rather than by whichever metric is biggest, phrasing the
override question honestly. A capable agent gets that right; the gates exist
for the failure mode where it doesn't.

## The Outbound Pipeline

Single drafts are the simple path. Campaigns, scrape a job board, build a
prospect list, personalize at volume, track replies, run through four layers in
a fixed order, each with a defined data shape, so an agent picking the work up
mid-campaign can reconstruct state from disk instead of from memory.

[[graph]]
{
  "nodes": [
    { "id": "scrape", "label": "Scrape", "row": 0, "col": 0, "note": "Scrapling" },
    { "id": "store", "label": "Store", "row": 0, "col": 1, "note": "runs/ + Notion" },
    { "id": "render", "label": "Render", "row": 0, "col": 2, "note": "cli.py render" },
    { "id": "track", "label": "Track", "row": 0, "col": 3, "note": "Notion status" },
    { "id": "f1", "label": "prospects_raw.json", "row": 1, "col": 0, "isolated": true },
    { "id": "f2", "label": "Notion: Researched", "row": 1, "col": 1, "isolated": true },
    { "id": "f3", "label": "Notion: Draft Ready", "row": 1, "col": 2, "isolated": true },
    { "id": "f4", "label": "Sent → Replied/Closed", "row": 1, "col": 3, "isolated": true }
  ],
  "edges": [
    { "from": "scrape", "to": "store", "label": "prospects_raw.json" },
    { "from": "store", "to": "render", "label": "prospects_enriched.json" },
    { "from": "render", "to": "track", "label": "outbounds.json" },
    { "from": "scrape", "to": "f1", "kind": "dashed" },
    { "from": "store", "to": "f2", "kind": "dashed" },
    { "from": "render", "to": "f3", "kind": "dashed" },
    { "from": "track", "to": "f4", "kind": "dashed" }
  ],
  "legend": [
    { "kind": "solid", "label": "pipeline handoff" },
    { "kind": "dashed", "label": "on-disk / Notion state" }
  ],
  "caption": "Fixed order, fixed shape per layer. An agent resuming a campaign reads state instead of re-deriving it."
}
[[/graph]]

Scraping escalates only as far as the page requires, a static `extract get`
before a JS-rendering `fetch` before an anti-bot `stealthy-fetch`. Layer 2
splits system of record from working copy on purpose: Notion carries status and
follow-up dates for a human to see, `prospects_enriched.json` is the shape an
agent actually reads from. Layer 3 never touches Notion or the scrape files
directly, it maps one enriched record to `--field` values and renders, so
render stays a pure, side-effect-free function you can preview before any Gmail
call. Layer 4 is a fixed status machine, `Prospect → Researched → Draft Ready →
Sent → Replied | Closed`, with follow-ups computed by query rather than by a
human remembering to check.

LinkedIn notes never get automated. They're rendered like everything else, but
sending one is a manual, human action in the LinkedIn UI, because the channel
doesn't offer an API for it and Wingmate isn't going to pretend otherwise.

## It Learns, On Purpose, In the Right Place

The interesting design decision isn't that Wingmate improves. It's *where* a
correction goes, because writing it in the wrong place is how most
personalization systems rot:

- A correction about **wording** goes into the template.
- A correction about **facts** goes into `identity.yaml`.
- A correction about **how the agent should work** goes into `AGENTS.md §12.5`,
  a section that ships empty by design, the seed file only carries defaults
  that are true for everyone.
- A one-off worth remembering but not yet worth committing to lives in a
  skill's git-ignored `SCRATCHPAD.md`.

The enforcement mechanism for actually noticing a pattern instead of drifting
past it is the **3rd-time rule**: every draft is recorded to
`runs/draft_monitor.json`, keyed by template and recipient, and the third
generation of the same email trips an `over_threshold` flag in the draft's own
output. That's the signal to stop regenerating and work a short checklist,
missed pattern, hallucination, identity drift, wrong template, rather than
trying a fourth time on instinct. Three retries of the same email is the system
telling you the problem is upstream, not in this draft.

## Why It's Structured This Way, Not As a SaaS Agent

Wingmate is deliberately not a hosted platform. A few consequences fall out of
that:

- **No model vendor lock-in.** There's no API key to configure because there's
  no call to make; whatever agent you already pay for supplies the reasoning.
- **Local-first and inspectable.** `data/`, `templates/`, and `runs/` are plain
  files. You can read exactly what will be said before it's said, and diff what
  changed between drafts.
- **Drafts by default, everywhere.** `draft` is the verb every code path
  reaches for; `send` is a separate command, requires an explicit recipient,
  and is still gated the same way `draft` is. Nothing leaves the account
  without a human choosing it.
- **It doesn't try to be a CRM.** No pipeline board, no sequencer, no built-in
  sender identity beyond your own Gmail account. Notion is optional tracking,
  not a dependency.

## Try It

The repo ships as a seed, `data/` describes a fictional developer and
`templates/` holds one email and its LinkedIn counterpart. The first session
with any agent on a fresh clone is an interview: replace that seed identity
with your own, rewrite the seed template until you'd actually send it, then
point Gmail at your own OAuth client and start drafting.

> Wingmate started as a way to stop dreading cold outreach, not as a product.
> If it saves you the same afternoon it saved me, or you just want to poke at
> the gates and see where they hold, I'd genuinely love to hear about it.
>
> — Krish

## WorldBench

A human-as-judge benchmark for whether a model can build a coherent world. One ~3,000 token prompt, one self-contained Three.js island per model, compared side by side.

Project URL: https://worldbench.krishb.tech/
GitHub: https://github.com/KrishBakshi/worldbench

Technologies: Next.js, TypeScript, Three.js, Tailwind CSS, MDX, Vercel

## Overview

**WorldBench** asks one question: can a model build a world that holds together?

Every model gets the same natural-language prompt, **~3,000 tokens**, and has to return a single self-contained `world.html` that renders a floating, multi-biome voxel island in **Three.js**. No image, no schema, no reference file, no build step. It has to open in a browser and run.

It is a **human-as-judge benchmark**. No rubric, no automated scorer, because there is no single correct island: only a hand-tuned reference built by a human, and your own eyes to compare it against a range of very different automated attempts.

---

## Why It Is Hard

The island is the visible part. What the prompt actually demands is that several kinds of reasoning hold at the same time:

- **Spatial and relational placement**: the prompt encodes a placement graph, not a list. The model has to satisfy the whole adjacency structure, not place ten biomes wherever they fit.
- **Ecological causality**: water has to behave like water. Mountain melt feeds a wet corridor. The desert is a rain-shadow basin with no through-river. Lava touching water quenches to obsidian. It is a small causal system to simulate, not decorate.
- **Elevation and scale**: peaks dominate, shelves sit mid-height, plains stay low, and every biome still has to read as its own region from a high orbit instead of collapsing into its neighbor.
- **Ecological grounding**: cacti in the desert, pines in the snow forest, deer at jungle edges. Placing life that matches its climate.
- **Temporal systems**: a day/night cycle, a seasonal cycle, and cyclic weather that stays staggered by biome instead of blanketing the island at once.
- **Interaction logic**: a clickable legend that flies the camera to each region, wiring 3D scene state to on-screen UI.

All of it in one file, first try.

---

## The Placement Graph

The ecology is a typed graph rather than prose. One perennial water corridor runs the length of it, and the edges that break that line carry the difficulty.

[[graph]]
{
  "nodes": [
    { "id": "mountains", "label": "Snow Mountains", "row": 0, "col": 1 },
    { "id": "forest", "label": "Snowy Conifer Forest", "row": 1, "col": 1 },
    { "id": "highlands", "label": "Highlands", "row": 2, "col": 0 },
    { "id": "volcano", "label": "Volcano", "row": 2, "col": 3, "note": "lava, not water", "isolated": true },
    { "id": "jungle", "label": "Dense Jungle", "row": 3, "col": 1 },
    { "id": "swamp", "label": "Backwater Swamp", "row": 3, "col": 2, "note": "dead end", "isolated": true },
    { "id": "grove", "label": "Flowering Grove", "row": 4, "col": 0 },
    { "id": "grassland", "label": "Grassland Plateau", "row": 4, "col": 1 },
    { "id": "desert", "label": "Desert Basin", "row": 4, "col": 3, "note": "rain shadow", "isolated": true },
    { "id": "delta", "label": "Coastal Delta / Ocean", "row": 5, "col": 1 }
  ],
  "edges": [
    { "from": "mountains", "to": "forest" },
    { "from": "forest", "to": "highlands" },
    { "from": "highlands", "to": "jungle" },
    { "from": "jungle", "to": "grassland" },
    { "from": "grassland", "to": "delta" },
    { "from": "jungle", "to": "swamp" },
    { "from": "highlands", "to": "grove", "kind": "dashed" },
    { "from": "grassland", "to": "grove", "kind": "dashed", "label": "and/or", "labelDy": -12 },
    { "from": "grassland", "to": "desert", "kind": "dotted", "label": "dry washes, fade out", "labelDy": -12 },
    { "from": "jungle", "to": "volcano", "kind": "causal", "label": "water + lava to obsidian", "labelDy": -12 }
  ],
  "legend": [
    { "kind": "solid", "label": "required water corridor" },
    { "kind": "dashed", "label": "alternate feed" },
    { "kind": "dotted", "label": "dry wash, never arrives" },
    { "kind": "causal", "label": "causal contact" }
  ],
  "caption": "The prompt's ecology, drawn as edges rather than prose."
}
[[/graph]]

The flowering grove has two valid sources, down the highland slope and/or across from the grassland plateau, which gives the graph its one real cycle. The desert touches the grassland only through dry washes that fade out before they arrive: a genuine relation, but never a through-river. The backwater swamp hangs off the jungle's wet side as a dead end. The volcano sits off the water system entirely, reached by a single causal rule.

Holding that adjacency structure while laying out geometry is the benchmark.

---

## Results

A growing roster, currently spanning `Claude`, `GPT`, `Gemini`, `Grok`, `Kimi`, `GLM` and `Qwen`, with new models added as they ship.

Worlds render live and interactive on the site, and up to six can be compared side by side.

---

## What's Next

Judgement today is **human** and whole-island: you look at it and decide whether it holds together. That resolves the question at only one level of detail.

Next is to go finer, either an **LLM-as-judge** or a per-component breakdown scoring **logical alignment**, **rendering**, **flora and fauna** and **placement graph** separately, so a world can be strong in one and weak in another instead of collapsing to a single impression. Making those scores mean anything takes heavy iteration, so it is near-future rather than now.

---

## Try It

Browse the results at [worldbench.krishb.tech](https://worldbench.krishb.tech/), or run it without the site: copy the prompt, paste it into any model, save the raw output as `world.html`, and open it in your browser.

## Open-Weight Voice AI Agent

A fully local voice agent pipeline built on open-weight models — Whisper STT, Gemma LLM, OmniVoice TTS, and browser-side Silero VAD — orchestrated via PipeCat with both live terminal and web UI modes.

Project URL: https://github.com/KrishBakshi/voice-agent-pipeline
GitHub: https://github.com/KrishBakshi/voice-agent-pipeline

Technologies: Python, PipeCat, Whisper, MLX, HuggingFace, Gemma, OmniVoice, Silero VAD, Socket.io, JavaScript, YAML

## Overview

Built a **fully local voice agent pipeline** using open-weight models, orchestrated through [PipeCat](https://github.com/pipecat-ai/pipecat). The system wires together speech-to-text, a language model, and text-to-speech into a continuous voice interaction loop — with no reliance on proprietary API-locked voice infrastructure.

The pipeline runs two modes: a **live terminal session** using local mic and speakers, and a **browser UI** with push-to-talk and VAD-driven audio capture over Socket.IO.

---

## Why This Project

Most voice agent demos depend on cloud-hosted STT and TTS APIs that are proprietary, rate-limited, or unsuitable for local experimentation. This project builds the full pipeline from open-weight components that can run on a Mac, making it possible to iterate on any part of the stack — VAD sensitivity, TTS voice design, LLM prompt tuning — without external dependencies.

---

## Pipeline Architecture

[[graph]]
{
  "nodes": [
    { "id": "stt", "label": "STT", "row": 0, "col": 0, "note": "Whisper" },
    { "id": "userctx", "label": "User context aggregator", "row": 0, "col": 1 },
    { "id": "llm", "label": "LLM", "row": 0, "col": 2, "note": "Gemma" },
    { "id": "tts", "label": "TTS", "row": 0, "col": 3, "note": "OmniVoice" },
    { "id": "asstctx", "label": "Assistant context aggregator", "row": 0, "col": 4 }
  ],
  "edges": [
    { "from": "stt", "to": "userctx" },
    { "from": "userctx", "to": "llm" },
    { "from": "llm", "to": "tts" },
    { "from": "tts", "to": "asstctx" }
  ],
  "caption": "Each stage is a modular PipeCat service, independently configurable through YAML files in config/."
}
[[/graph]]

---

## Components

### STT — Whisper (MLX / Faster Whisper)

- Uses **MLX Whisper** on Apple Silicon for hardware-accelerated transcription.
- Falls back to **Faster Whisper** on non-Apple hardware.
- Backend selection is automatic via `config/stt.yaml` (`backend: auto`).
- Supports live mic transcription and batch file transcription from CLI.

### LLM — Gemma 4 (open-weight via Google API)

- Uses **gemma-4-26b-a4b-it** as the reasoning layer through PipeCat's Google LLM service.
- System prompt in `config/llm.yaml` is tuned for TTS-friendly output: short sentences, spoken phrasing, minimal formatting, and light use of non-verbal expression tags.
- Supported tags baked into the prompt include: `[laughter]`, `[sigh]`, `[confirmation-en]`, `[question-en]`, `[surprise-ah]`, and others.

### TTS — OmniVoice

- Custom PipeCat `TTSService` backed by **OmniVoice** for voice synthesis.
- Supports voice design through the `instruct` field in `config/tts.yaml` using attribute strings like `female, low pitch, indian accent, young adult`.
- OmniVoice instruct syntax uses comma-space separators and must be composed from supported items only — not free-form prose.
- Currently pinned to CPU due to MPS instability on Mac; MPS path exists in code.

### VAD — Silero (Browser + Server)

Two separate VAD implementations depending on mode:

**Web mode (browser-side)**
- Uses [`@ricky0123/vad-web@0.0.29`](https://github.com/ricky0123/vad) loaded from jsDelivr CDN alongside `onnxruntime-web@1.22.0`.
- Silero model runs as ONNX WASM entirely inside the browser — no server VAD compute involved.
- `onSpeechEnd` fires when silence is detected; the complete Float32Array segment (16 kHz) is converted to 16-bit PCM and emitted over Socket.IO as `vad_speech_end`.

**Live terminal mode (server-side)**
- Uses PipeCat's `SileroVADAnalyzer` (`pipecat.audio.vad.silero`) wrapped in a `VADProcessor`.
- Bundled inside `pipecat-ai`; no separate install required.
- Parameters configurable via `config/session.yaml`: `vad_confidence`, `vad_start_secs`, `vad_stop_secs`, `vad_min_volume`.

---

## Modes

[[graph]]
{
  "nodes": [
    { "id": "mic", "label": "microphone", "row": 0, "col": 0 },
    { "id": "vad-live", "label": "Silero VAD", "row": 0, "col": 1, "note": "server, PipeCat" },
    { "id": "stt-live", "label": "STT", "row": 0, "col": 2 },
    { "id": "llm-live", "label": "Gemma LLM", "row": 0, "col": 3 },
    { "id": "tts-live", "label": "OmniVoice TTS", "row": 0, "col": 4 },
    { "id": "speakers", "label": "speakers", "row": 0, "col": 5 },

    { "id": "bmic1", "label": "browser mic", "row": 1, "col": 0 },
    { "id": "spn", "label": "ScriptProcessorNode", "row": 1, "col": 1, "note": "push-to-talk" },
    { "id": "sio1", "label": "Socket.IO", "row": 1, "col": 2 },
    { "id": "backend1", "label": "STT → Gemma → OmniVoice", "row": 1, "col": 3 },
    { "id": "playback1", "label": "browser playback", "row": 1, "col": 4 },

    { "id": "bmic2", "label": "browser mic", "row": 2, "col": 0 },
    { "id": "vadweb", "label": "vad-web", "row": 2, "col": 1, "note": "Silero WASM, live detection" },
    { "id": "sio2", "label": "Socket.IO", "row": 2, "col": 2 },
    { "id": "backend2", "label": "STT → Gemma → OmniVoice", "row": 2, "col": 3 },
    { "id": "playback2", "label": "browser playback", "row": 2, "col": 4 }
  ],
  "edges": [
    { "from": "mic", "to": "vad-live" },
    { "from": "vad-live", "to": "stt-live" },
    { "from": "stt-live", "to": "llm-live" },
    { "from": "llm-live", "to": "tts-live" },
    { "from": "tts-live", "to": "speakers" },

    { "from": "bmic1", "to": "spn" },
    { "from": "spn", "to": "sio1" },
    { "from": "sio1", "to": "backend1" },
    { "from": "backend1", "to": "playback1" },

    { "from": "bmic2", "to": "vadweb" },
    { "from": "vadweb", "to": "sio2" },
    { "from": "sio2", "to": "backend2" },
    { "from": "backend2", "to": "playback2" }
  ],
  "caption": "Row 1: live terminal session. Row 2: browser push-to-talk. Row 3: browser VAD-driven. All three share the same STT → Gemma → OmniVoice core."
}
[[/graph]]

Live terminal uses PipeCat's local audio transport with PyAudio; interruptions
are disabled by default to avoid speaker-to-mic bleed on Mac. Both browser
modes send a single base64-encoded WAV back over Socket.IO per turn — not
full-duplex streaming, one complete response per utterance.

---

## Mac Runtime Notes

| Component | Accelerator |
|-----------|------------|
| Whisper STT | MLX on Apple Silicon |
| OmniVoice TTS | CPU (MPS path exists but unstable) |
| VAD (web) | Browser WASM — no server compute |
| VAD (live) | Server CPU via PipeCat Silero |
| Gemma LLM | Google API (open-weight, not locally run) |

---

## Config System

All service configuration is driven by YAML files:

- `config/stt.yaml` — backend selection, model settings
- `config/llm.yaml` — system prompt, model name
- `config/tts.yaml` — voice instruct, device pin
- `config/session.yaml` — VAD parameters, transport settings
- `config/web.yaml` — browser UI host/port

Environment variables override sensitive values: `GEMINI_API_KEY`, `GEMINI_MODEL`, `VOICE_AGENT_LANGUAGE`.

---

## CLI Commands

```bash
# Describe the configured stack
uv run python main.py --describe

# Synthesize a TTS sample
uv run python main.py --synthesize "Hello, this is a local Pipecat voice agent." --output out.wav

# Transcribe an audio file
uv run python main.py --config-dir config --transcribe path/to/audio.wav

# Live mic STT test
uv run python main.py --config-dir config --live-stt

# Start a live local session
uv run python main.py --config-dir config --live

# Start the browser UI
uv run python main.py --config-dir config --web
```

---

## Status

The pipeline is **working end-to-end** in both live and web modes. The current transport is interactive but not full-duplex streaming — upstream is PCM chunks over Socket.IO, downstream is one complete WAV response per turn. Streaming TTS playback and barge-in support are the natural next extensions.

## LinkedIn Research Agent

A Codex-style sourcing assistant that builds LinkedIn Boolean queries, navigates People search via MCP browser automation, and returns clean profile URL lists with optional structured profile extraction.

Project URL: https://github.com/KrishBakshi/linkedin_research_agent
GitHub: https://github.com/KrishBakshi/linkedin_research_agent

Technologies: Codex, Node.js, CLI, MDX, MCP, LinkedIn, Perplexity, Chrome DevTools MCP, LinkedIn Boolean Search

## Overview

Built a **LinkedIn sourcing workflow agent** that turns role requests into structured search operations: it parses hiring intent, generates Boolean query logic, executes LinkedIn People search in an MCP-controlled browser, and returns **plain profile URLs** for downstream recruiting or research pipelines.

The project is designed around reproducible, instruction-driven agent workflows with optional enrichment mode that extracts public profile metadata (name, role, company, headline, location, company link, and public contact fields) from individual profile URLs.

---

## Why This Project

Manual LinkedIn sourcing is repetitive and hard to standardize across searches. This project converts that process into a deterministic agent flow so output quality is consistent: query construction follows clear rules, search navigation is scripted, and results can be saved to timestamped JSON runs.

It also separates discovery from enrichment: Comet navigation collects candidate URLs efficiently, while Chrome DevTools-based extraction handles deeper profile parsing when needed.

---

## How a Search Runs

[[graph]]
{
  "nodes": [
    { "id": "intent", "label": "Hiring intent", "row": 0, "col": 0 },
    { "id": "boolean", "label": "Boolean query builder", "row": 0, "col": 1, "note": "includes/excludes, title, location" },
    { "id": "search", "label": "People search", "row": 0, "col": 2, "note": "MCP browser control" },
    { "id": "urls", "label": "Profile URLs", "row": 0, "col": 3 },
    { "id": "extract", "label": "Profile extraction", "row": 1, "col": 3, "note": "optional, Chrome DevTools MCP" },
    { "id": "runs", "label": "runs/*.json", "row": 0, "col": 4, "isolated": true }
  ],
  "edges": [
    { "from": "intent", "to": "boolean" },
    { "from": "boolean", "to": "search" },
    { "from": "search", "to": "urls" },
    { "from": "urls", "to": "extract", "kind": "dotted", "label": "if enrichment" },
    { "from": "urls", "to": "runs", "kind": "dashed" },
    { "from": "extract", "to": "runs", "kind": "dashed" }
  ],
  "legend": [
    { "kind": "solid", "label": "default path" },
    { "kind": "dotted", "label": "optional step" },
    { "kind": "dashed", "label": "saved artifact" }
  ],
  "caption": "Discovery always runs; enrichment only fires when profile extraction is requested."
}
[[/graph]]

## Key Capabilities

- **Boolean Query Builder**
  - Converts user intent into compact LinkedIn-friendly Boolean strings.
  - Supports includes, excludes, title phrases, and location intent.

- **People Search Navigation**
  - Applies LinkedIn search + People filter through MCP browser actions.
  - Collects profile URLs from result pages in strict plain-text format.

- **Optional Profile Extraction**
  - Given profile URLs, extracts structured public fields for analysis.
  - Returns empty values for missing fields instead of guessing.

- **Run Artifacts**
  - Saves timestamped JSON outputs in a dedicated `runs/` directory.
  - Keeps collected data separate from source instructions and skills.

---

## Status

The project is **working and extensible**, with clear skill-based modules for query generation, search-page navigation, and profile extraction.

## YOLO ML Utils

A practical utility toolkit for YOLO-based computer vision workflows, covering dataset preparation, annotation processing, visualization, and training/debug utilities used in real-world ML pipelines.

Project URL: https://github.com/KrishBakshi/yolo-ml-utils
GitHub: https://github.com/KrishBakshi/yolo-ml-utils

Technologies: Python, Gradio, OpenCV, NumPy, Ultralytics, Computer Vision

## Overview

**YOLO ML Utils** is a modular collection of helper scripts and utilities built to **streamline end-to-end YOLO computer vision workflows**.  
It focuses on eliminating repetitive boilerplate involved in dataset handling, annotation management, visualization, and training/debug cycles.

The toolkit is designed for **rapid experimentation, cleaner pipelines, and production-friendly workflows**, especially when working with custom datasets and iterative model training.

---

## Why This Project

Working with YOLO models often involves:
- Repeated dataset restructuring
- Manual annotation sanity checks
- Debugging incorrect bounding boxes or masks
- Writing ad-hoc scripts for visualization and validation

This repository consolidates those recurring tasks into **reusable, consistent utilities**, enabling faster iteration and fewer data-related training failures.

---

## Key Capabilities

- **Dataset Utilities**
  - Dataset restructuring and format normalization for YOLO training
  - Train/validation/test split handling
  - File integrity and consistency checks

- **Annotation Handling**
  - Parsing and validating YOLO annotation files
  - Coordinate normalization and conversion helpers
  - Detection of corrupted or misaligned labels

- **Visualization & Debugging**
  - Bounding box and annotation overlays on images
  - Visual inspection tools to catch labeling errors early
  - Lightweight OpenCV-based rendering for fast checks

- **Training Support**
  - Utilities to assist during training and evaluation cycles
  - Debug helpers for common YOLO data-related issues
  - Designed to plug into existing YOLO pipelines with minimal setup

---

## Design Philosophy

- **Utility-first**: Small, focused scripts that do one job well  
- **Composable**: Functions can be chained into larger pipelines  
- **Framework-agnostic**: Compatible with Ultralytics YOLO and custom training loops  
- **Production-aware**: Built from real experimentation and fine-tuning workflows, not toy examples  

---

## Use Cases

- Rapid prototyping of custom YOLO datasets  
- Debugging bounding box or annotation issues before long training runs  
- Standardizing dataset pipelines across multiple experiments  
- Supporting research, internships, and production ML vision projects  

---

## Status

The repository is **actively usable and extensible**, with utilities added as new YOLO-related needs arise during experimentation and model development.

## Flappy Bird DQN

A reinforcement learning experiment implementing Deep Q-Learning (DQN) to train an agent that learns to play Flappy Bird from raw game frames—with reward shaping, experience replay, and ε-greedy exploration.

Project URL: https://github.com/KrishBakshi/rl-exp/tree/master/deep_q_learning
GitHub: https://github.com/KrishBakshi/rl-exp/tree/master/deep_q_learning

Technologies: Python, PyTorch, Reinforcement Learning, Deep Q-Learning (DQN), OpenAI Gym, NumPy

## Overview

Built a **reinforcement learning (RL) agent using Deep Q-Learning (DQN)** that learns to play **Flappy Bird** directly from pixel inputs. The project demonstrates core RL principles—**state representation from raw frames, experience replay, target networks, and ε-greedy exploration**—to gradually learn optimal policies in a challenging, high-variance environment.

The agent successfully learns to keep the bird alive, navigating pipes by learning when to flap and when to glide, purely through reward-driven trial and error.

---

## Why This Project

Flappy Bird presents a rich RL challenge due to sparse rewards, high-dimensional pixel input, and noisy transitions: simple policies fail quickly, and naive training diverges. Applying Deep Q-Learning in this setting showcases:

- Effective use of **neural value approximation**  
- Stabilization via **replay buffers and target networks**  
- Practical exploration strategies to balance discovery and exploitation

This experiment solidifies understanding of RL algorithms in environments with visual state spaces and delayed rewards.

---

## Key Components

- **State Representation**
  - Input composed of preprocessed stacked frames to capture motion dynamics.
  - Efficient grayscale + resizing for low-dimensional RL input.

- **Deep Q-Network (DQN)**
  - Convolutional neural network (CNN) to approximate Q-values.
  - PyTorch implementation for flexibility and training control.

- **Experience Replay**
  - Memory buffer that stores transitions for decorrelated training samples.
  - Mini-batch sampling for stable gradient updates.

- **Target Network**
  - Separate target network to reduce oscillations and divergence.
  - Periodic synchronization from online network.

- **Exploration Strategy**
  - ε-greedy policy with decay to balance exploration and exploitation.

- **Reward Engineering**
  - Shaping and clipping to ensure useful learning signals evolve.

---

## How It Works

[[graph]]
{
  "nodes": [
    { "id": "env", "label": "Gym env", "row": 0, "col": 0, "note": "pixel frames, reward" },
    { "id": "prep", "label": "Preprocess", "row": 0, "col": 1, "note": "grayscale, resize, stack" },
    { "id": "policy", "label": "ε-greedy policy", "row": 0, "col": 2, "note": "decays over episodes" },
    { "id": "action", "label": "Action", "row": 0, "col": 3, "note": "flap / no-op" },
    { "id": "buffer", "label": "Replay buffer", "row": 1, "col": 1, "note": "(s, a, r, s')" },
    { "id": "cnn", "label": "DQN (CNN)", "row": 1, "col": 2, "note": "Q-value approximation" },
    { "id": "target", "label": "Target network", "row": 1, "col": 3, "isolated": true }
  ],
  "edges": [
    { "from": "env", "to": "prep" },
    { "from": "prep", "to": "cnn" },
    { "from": "cnn", "to": "policy" },
    { "from": "policy", "to": "action" },
    { "from": "action", "to": "env", "kind": "dotted", "label": "next step" },
    { "from": "prep", "to": "buffer", "kind": "dashed", "label": "store transition" },
    { "from": "buffer", "to": "cnn", "label": "mini-batch sample" },
    { "from": "target", "to": "cnn", "kind": "dashed", "label": "Bellman target" },
    { "from": "cnn", "to": "target", "kind": "dotted", "label": "periodic sync" }
  ],
  "legend": [
    { "kind": "solid", "label": "forward pass" },
    { "kind": "dashed", "label": "storage / target" },
    { "kind": "dotted", "label": "feedback loop" }
  ],
  "caption": "Every step both acts in the environment and trains from a sampled batch; the target network only updates periodically to keep learning stable."
}
[[/graph]]

---

## Results

The trained agent gradually improves survivability:
- Early episodes show frequent crashes.
- With training, the agent **learns to navigate pipes consistently**, maintaining high average episode length.
- Visualization confirms intelligent flap timing and avoidance of collisions.

---

## Design Highlights

- **Modular Codebase**
  - Clear separation between environment handling, agent logic, memory buffer, and training loop.
  - Easy to extend for other RL algorithms (e.g., Double DQN, Dueling Networks).

- **Training Monitoring**
  - Logging of episodic scores and losses to track progress and debug learning dynamics.

- **Policy Replay**
  - Optional gameplay rendering to inspect agent behavior qualitatively.

---

## Use Cases

- Reinforcement-learning benchmarking in environments with pixel inputs.  
- Teaching and experimentation with classical DQN vs improved variants.  
- Research base for extending to **Double DQN, Prioritized Replay, or A3C/PPO**.

---

## Status

The experiment is **fully working and reproducible**, with scripts and utilities to train from scratch or play back trained models.

## SEC Filings QA Agent

A semantic question-answering system for SEC filings (10-K, 8-K, DEF 14A, etc.) using LangChain, vector retrieval, and Gemini Flash for deep financial research workflows.

Project URL: https://github.com/KrishBakshi/sec-filings-qa-agent
GitHub: https://github.com/KrishBakshi/sec-filings-qa-agent

Technologies: Python, LangChain, ChromaDB, GoogleGemini, HuggingFace, Streamlit

## Overview

Built a **semantic Q&A system for SEC filings** that lets users ask natural language questions over regulatory financial documents such as **10-K, 8-K, and DEF 14A** reports.  
The system combines **retrieval-augmented generation (RAG)** with vector search (ChromaDB), contextual embeddings, and a lightweight Streamlit interface to deliver fast, accurate, and attributed answers across multiple companies’ filings.

It’s designed for **deep financial research** — enabling both analysts and engineers to query dense corporate disclosures with simple queries like *“What are Apple’s risk factors in the latest 10-K?”* or *“How has Tesla described climate-related risks?”*.

---

## Why This Project

Traditional analysis of SEC filings is labor-intensive: filings often exceed hundreds of pages, and pulling insights manually can take hours. By integrating **large language models with retrieval systems**, this project automates the heavy lifting: it extracts context from long documents and grounds responses in the exact source text. This **reduces ambiguity, improves accuracy, and scales document understanding** far beyond keyword search.

---

## Key Capabilities

- **Semantic Question Answering**
  - Users can ask complex natural language questions about financial reports.
  - Responses are grounded in the context of relevant filings, improving relevance and trustworthiness.

- **RAG Pipeline Integration**
  - Documents are chunked and embedded using `sentence-transformers`.
  - A ChromaDB vector store enables fast retrieval of semantically relevant text passages.

- **Metadata-Driven Attribution**
  - Answers include contextual metadata like **ticker, date, section, and filing type**, helping users verify responses against original sources.

- **Interactive UI**
  - Streamlit-based interface for quick explorations, chain queries, and interactive research.

---

## How It Works

[[graph]]
{
  "nodes": [
    { "id": "meta", "label": "Metadata collection", "row": 0, "col": 0, "note": "SEC APIs" },
    { "id": "prep", "label": "Preprocessing", "row": 0, "col": 1, "note": "clean + flatten" },
    { "id": "chunk", "label": "Chunk & embed", "row": 0, "col": 2, "note": "sentence-transformers" },
    { "id": "index", "label": "ChromaDB index", "row": 0, "col": 3 },
    { "id": "qa", "label": "QA pipeline", "row": 0, "col": 4, "note": "LangChain + Gemini Flash" },
    { "id": "ui", "label": "Streamlit UI", "row": 0, "col": 5, "note": "attributed answers" }
  ],
  "edges": [
    { "from": "meta", "to": "prep" },
    { "from": "prep", "to": "chunk" },
    { "from": "chunk", "to": "index" },
    { "from": "index", "to": "qa", "label": "retrieval" },
    { "from": "qa", "to": "ui" }
  ],
  "caption": "Filings move through ingestion once; every user question only re-runs retrieval and generation."
}
[[/graph]]

---

## Use Cases

- **Corporate Financial Research**  
  Quickly analyze risk disclosures, executive compensation, or segment performance across years and companies.

- **Investor Insights**  
  Surface high-impact information from filings before key events like earnings or shareholder meetings.

- **Education & Data Exploration**  
  Enable finance students and researchers to ask interpretive questions on regulatory filings without manual reading.

---

## Design Highlights

- **Attribution-Focused Answers**  
  Source metadata travels with the text chunks to ensure that answers link back to precise parts of filings.

- **Conversational Memory**  
  Supports follow-up questions that build on context from previous queries.

- **Modular & Extensible**  
  Each phase of the pipeline (ingestion, preprocessing, retrieval, LLM calling) is modular, making custom extensions straightforward.

---

## Sample Questions

- “What are Apple’s risk factors in the latest 10-K?”
- “Compare R&D spending of Tesla and Microsoft.”
- “Describe climate-related risk disclosures for JPMorgan.”
- “How was executive compensation updated for UNH?”

---

## Status

This project is a functional research prototype, with scope to extend UI filters, evaluate model accuracy, and add advanced search capabilities.

## AutoMailAI

AI-powered cold email generator with prompt engineering, dynamic templates, and Gmail auto-drafting. It helped me secure 3 internship offers.

Project URL: https://huggingface.co/spaces/krishbakshi/AutoMailAI
GitHub: https://github.com/KrishBakshi/AutoMailAI

Technologies: Python, LangChain, GoogleGemini, Google Cloud Platform, Gmail API, Gradio

## Overview

Built an AI-powered cold email generator with prompt engineering, dynamic templates, and Gmail auto-drafting. **It helped me secure 3 internship offers** through personalized outreach.

## ImaginAIry

Text-to-image generation pipeline using Stable Diffusion XL with prompt augmentation via Gemini 2.0 Flash.

Project URL: https://www.linkedin.com/posts/krish-bakshi-8b85b6314_even-with-a-state-of-the-art-fine-tuned-image-activity-7298677844761587712-Lcuv
GitHub: https://github.com/KrishBakshi/ImaginAIry

Technologies: Python, HuggingFace, PyTorch, Stable Diffusion XL, GoogleGemini, Gradio, Text-to-Image

## Overview

Built a text-to-image generation pipeline using Stable Diffusion XL with prompt augmentation via Gemini 2.0 Flash. Optimized it for local light weight inference.

## LLM-Powered Dashboard

Realtime Analytics dashboard powered by LLM Insights. Queries BigQuery datasets and generates insights using Gemini 2.0 Flash.

Project URL: https://motor-llmdashboard.streamlit.app/
GitHub: https://github.com/KrishBakshi/LLM_Dashboard/tree/master

Technologies: Python, PySpark, Pandas, Plotly, GoogleGemini, GoogleBigQuery, Streamlit

## Overview

Real-time analytics dashboard powered by LLM insights. It queries BigQuery datasets and generates summaries using Gemini 2.0 Flash, with an interface for data exploration and visualization.

## KisanAI

Smart assistant for farmers that gives crop health insights and personalized tips using YOLOv5, EfficientNet-B0, and GPT-4.

Project URL: https://kisan-ai-krish-bakshis-projects.vercel.app/
GitHub: https://github.com/KrishBakshi/KisanAI

Technologies: Python, TypeScript, Flask, React, Next.js, Vercel, OpenAI, Google Cloud Platform

## Overview

KisanAI is a smart assistant for farmers that gives crop health insights and personalized tips using YOLOv5, EfficientNet-B0, and GPT-4. It helps with better decisions and government scheme awareness.
