{%- if collections.posts.length %} {%- endif %} {% include "partials/head-assets.njk" %} {% include "partials/site-header.njk" %}

Jagrit Vats

Backend engineer at Bajaj Finserv Health, based in Bangalore.

I lead a team building a new product from scratch on Spring Boot, and work on the integration layer that connects our services to Salesforce and other external systems over Kafka. Outside work I build and host my own apps, including Crossroom, where AI agents from different vendors work in one room, TeamTakes, a polling app for engineering teams, and Synpse, an AI companion with over 500 installs on Google Play.

Retry, then dead-letter An event moves from a Kafka topic to a consumer. If processing succeeds, it moves on to the next stage. If it fails, it goes to a retry topic and back to the consumer. When the retries are used up, it moves to a dead-letter queue so it can be replayed. consume succeeds fails retry retries used up Kafka topic workflow events Consumer Spring Boot Next stage processed Retry topic back off, try again Dead-letter queue parked for replay
A simplified version of the Kafka flows I work on. When a consumer can't process an event, the event goes to a retry topic and is tried again. After a few failed attempts it moves to a dead-letter queue, where it can be checked and replayed.

Work

{#- Work, Tools, Education and Contact are generated from _data/profile.json. -#} {%- for job in profile.jobs %} {%- set role = job | currentRole %}

{{ job.company }}

{%- if job.roles.length > 1 %}

{{ job | jobPeriod }}

{%- else %}

{{ role.title | sentenceCase }}, {{ job | jobPeriod("short") }}

{%- endif %}
{%- if job.roles.length > 1 %}
    {%- for r in job.roles %} {{ r.title }} {{ r | rolePeriod("short") }} {%- endfor %}
{%- endif %} {%- if job.highlights.length > 1 %}
    {%- for h in job.highlights %}
  • {{ h | pick("page") }}
  • {%- endfor %}
{%- else %}

{{ job.highlights[0] | pick("page") }}

{%- endif %}
{%- endfor %}

Projects

Crossroom Live

Puts up to six AI agents in one room, each working from its own files, and keeps the recap.

The agents can come from different vendors and run on different machines, under their owners' own sign-ins. You paste a room link into Claude Code or Codex, and any agent that can run curl or speak MCP can join. A room can share knowledge, split a change across repositories, hold a meeting or settle a question. When it ends, Crossroom writes the outcomes, decisions, actions and open questions from the room's event log.

  • Agents send each other excerpts of files and logs, and every excerpt carries its source path. An agent can't cite an excerpt it was never shown, and when Crossroom runs the agent itself, an excerpt that doesn't match the file gets flagged.
  • Each room is encrypted at rest under its own key, wrapped by the owner and seat tokens. The server keeps only a hash of the owner token, and rooms can be end-to-end encrypted so the server can't read them at all.
  • Crossroom Code runs Claude Code, Codex, OpenCode and Grok as chats on your own computer and lets a browser on another device carry on with them through an encrypted relay.

TypeScript, Hono, MCP SDK, SQLite (node:sqlite), Vite + React, TypeScript and Python SDKs, VS Code extension, Docker, Traefik, GitHub Actions

How a Crossroom room works Agents such as Claude Code, Codex or any MCP client join a room through seat links. In the room they exchange excerpts that carry their source paths. Every event is stored in SQLite under a per-room key, and when the room ends the minutes are rendered from the event log. seat links every event on close Claude Code Codex Any agent Room (Hono + MCP server) agents trade excerpts of files and logs, each labelled with its source path SQLite event log per-room key at rest Minutes from the log, signed the owner watches and can interject from a browser

TeamTakes Live

Opinionated polls and play-money prediction games for engineering teams.

Each team gets its own subdomain with polls, prediction markets and 1v1 duels. There's also a league that runs across teams. I first built it for one team and made it multi-tenant later, while people were already using it.

  • Row-level security limits every query to teams the user belongs to. The app sends the active team in a request header, and the database ignores it unless the user is a member of that team.
  • A leak test signs in as one team and tries to read and change another team's data through the normal API. A check in the migrations stops the deploy if a table is missing its policy.
  • Most of the game logic, including market pricing and the points ledger, runs in Postgres functions, with pg_cron handling scheduled rounds.

TanStack Start (React 19), self-hosted Supabase (Postgres, Auth, Realtime), Docker Compose, Traefik, Cloudflare, GitHub Actions

How TeamTakes keeps teams apart Requests from team A and team B reach the same Postgres database, each tagged with its active team in a header. Row-level security on every table only returns rows from teams the user belongs to, so a team A request comes back with team A's rows, while a team A user who tags a request with team B is blocked and gets nothing back. A leak test probes the database for exactly that, and a migration check confirms every table has a policy. x-team-id: A x-team-id: B not a member x-team-id: B probes checks Team A team-a.teamtakes.app Team B team-b.teamtakes.app Postgres (self-hosted Supabase) restrictive row-level security on every table Leak test signs in as team A, gets no rows from B Migration check deploy stops if any table lacks its policy

ScenePilot Hackathon project, 2026

An AI assistant director that reschedules film shoots when plans change.

I built it for Google Cloud's Agentic Cinema hackathon, in the Parallel partner track. When something disrupts a shoot day, like a rain forecast, Gemini agents look it up on the web and suggest new schedules. A deterministic rule checker then rejects any schedule that breaks a permit window, a noise curfew or the available daylight, and links the source for each rejection. The producer has to approve a change before it's applied.

  • Facts from the web are graded by how well they're sourced. Only a cited, high-confidence fact that the producer has accepted can reject a schedule. Weaker facts are shown for a person to check.
  • The same checker handles recovery options, the multi-day schedule and manual edits.
  • It uses six of Parallel's web APIs, calculates sun position and union labour rules in code, and has more than 600 tests.

Python, FastAPI, Google ADK, Gemini 3.5 Flash on Vertex AI, Parallel APIs, Next.js 16, Docker Compose on an ARM VM, Traefik, Cloudflare

How ScenePilot decides A rain forecast goes to Gemini agents built with Google ADK, which research it using Parallel's web APIs and suggest new plans. A rule checker tests each plan against sourced facts such as permit windows, noise curfews and sunset. Plan B is rejected because it runs past a curfew. Plan A is kept and waits for the producer to approve it. disruption plans cited facts Rain forecast for day 4 checked against live web sources Gemini agents research, propose plans Parallel APIs web research Rule checker permit windows, cited noise curfews, sunset Plan B rejected past a cited curfew Plan A kept waits for producer

Synpse Live since 2025

An AI companion with long-term memory, on the web, Android and Telegram.

The companion is called Synapy, and the Android app has over 500 installs on Google Play. Users can see, edit and delete what it remembers about them. It also writes daily and weekly journal entries.

  • Memories are ranked by how well they match the message, recency, importance, links to other memories, mood and context. Linked memories are pulled in as well.
  • A background job runs every four hours to merge similar memories and prune old ones.
  • The model provider can be switched between Grok, Gemini, OpenAI and local models through Ollama. Embedding calls have a timeout and a circuit breaker, and Kafka is optional, with an in-process fallback.

Next.js 14, Express, TypeScript, MongoDB, LangChain, Expo (React Native), grammY, Docker, GitHub Actions

How Synpse remembers The web app, Android app and Telegram bot share one Express API. For each message, a retrieval step ranks memories from MongoDB on six signals and builds the prompt for the language model. A background job consolidates memories every four hours. each message reads prompt Web app Android Telegram Express API streams replies as server-sent events Memory retrieval scores six signals: meaning, recency, importance, links to other memories, mood and context MongoDB memories, journals Language model Grok, Gemini or Ollama consolidation job every 4 hours

Also built in 2026

  • ResidentOps

    Handles maintenance issues for a residents' association. When several residents report different faults, it uses a map of the building's equipment to find a shared cause, asks contractors for quotes, and goes to the committee only when money or a judgement call is involved. Spending limits are checked in code, and blocked actions are logged.

    AWS Agents for Humans hackathon. Python, Strands Agents, Amazon Bedrock.

  • SAMAY

    Helps people in India respond to online fraud. It lists the urgent steps, like calling the 1930 helpline and the bank, keeps the evidence in one case, and prepares a report to file on cybercrime.gov.in.

    Build What Moves India hackathon. Next.js, Postgres.

  • Presentic

    A command-line tool that makes narrated demo videos from a JSON plan. Playwright clicks through the real app, Remotion adds captions and zoom, and scene lengths are matched to the narration. It can also run as an MCP server. I've used it for my hackathon demo videos.

    TypeScript, Playwright, Remotion. Private repository.

Older work includes an email template app for Shopify stores (2022), PresentKon for Hack the Mountains 3.0 (2022), the Presto e-commerce site (2022) and a merged fix to next-firebase-auth (2023).

{%- if collections.posts.length %}

Writing

All writing or follow the Atom feed.

{%- endif %}

Tools

{%- for skill in profile.skills %}
{{ skill.name | pick("page") }}
{{ skill.list | pick("page") }}
{%- endfor %}

Education

{%- for e in profile.education %}

{{ e.institution }}
{{ e | degreeName }}
{{ e | degreeSummary }}

{%- endfor %}

{{ profile.certificates.issuer }} certifications
{%- for c in profile.certificates.items %} {% if loop.last and not loop.first %}and {% endif %}{% if c.url %}{{ c.name }}{% else %}{{ c.name }}{% endif %}{% if loop.revindex > 2 %},{% endif %} {%- endfor %}

Contact

Email is the quickest way to reach me.

{% include "partials/site-footer.njk" %}