<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Risheek Mittal</title>
    <description>The latest articles on DEV Community by Risheek Mittal (@risheekmittal).</description>
    <link>https://dev.to/risheekmittal</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3997017%2F4bcd6465-c346-4b67-b591-4faa0ba28332.jpg</url>
      <title>DEV Community: Risheek Mittal</title>
      <link>https://dev.to/risheekmittal</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/risheekmittal"/>
    <language>en</language>
    <item>
      <title>How I orchestrated 6 Gemini agents with FastAPI and AlloyDB to automate job hunting</title>
      <dc:creator>Risheek Mittal</dc:creator>
      <pubDate>Thu, 25 Jun 2026 08:22:56 +0000</pubDate>
      <link>https://dev.to/risheekmittal/how-i-orchestrated-6-gemini-agents-with-fastapi-and-alloydb-to-automate-job-hunting-59l1</link>
      <guid>https://dev.to/risheekmittal/how-i-orchestrated-6-gemini-agents-with-fastapi-and-alloydb-to-automate-job-hunting-59l1</guid>
      <description>&lt;p&gt;Most "AI agent" tutorials I see online are glorified single-prompt wrappers. They work for a demo, but they fall apart the second you introduce state, memory, or concurrent execution.&lt;/p&gt;

&lt;p&gt;Last week, while pushing 30 commits to my core infrastructure, I realized that building a robust job-hunting engine isn't about the LLM—it’s about the orchestration layer. I built &lt;strong&gt;JobHunt.ai&lt;/strong&gt; using a 6-agent Gemini architecture, and I learned that without a rock-solid database layer like AlloyDB to manage state, your agents are just expensive random number generators.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Problem: Why Single-Prompt Agents Fail
&lt;/h3&gt;

&lt;p&gt;If you’re building an agent to automate job applications, you aren't just sending a string to an API. You are managing:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Context:&lt;/strong&gt; What did the previous agent decide?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persistence:&lt;/strong&gt; If the process crashes (and it will), can you resume?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concurrency:&lt;/strong&gt; Can you parse a resume, scrape a JD, and draft a cover letter simultaneously without blocking the event loop?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In my architecture, I moved away from simple script-based execution to a stateful, asynchronous pipeline.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Architecture: 6 Agents, 1 Source of Truth
&lt;/h3&gt;

&lt;p&gt;I designed JobHunt.ai around six specialized agents, each triggered by a FastAPI endpoint and coordinated via AlloyDB:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The Scraper:&lt;/strong&gt; Fetches JD data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Resume Parser:&lt;/strong&gt; Extracts entities via PaddleOCR/Gemini.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Matching Agent:&lt;/strong&gt; Scores the fit (Resume vs. JD).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Tailoring Agent:&lt;/strong&gt; Rewrites the bullet points.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Cover Letter Agent:&lt;/strong&gt; Generates the pitch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Validation Agent:&lt;/strong&gt; Performs a final "sanity check" against the JD requirements.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  The Stack
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Engine:&lt;/strong&gt; FastAPI (Asynchronous execution is non-negotiable).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Brain:&lt;/strong&gt; Google Gemini (via Google ADK).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Database:&lt;/strong&gt; AlloyDB (PostgreSQL-compatible, enterprise-grade handling of JSONB for agent states).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Orchestration:&lt;/strong&gt; Python &lt;code&gt;asyncio&lt;/code&gt; + n8n for workflow triggers.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Implementation: Managing State in AlloyDB
&lt;/h3&gt;

&lt;p&gt;The secret sauce is how I handle agent handoffs. I don't pass massive objects in memory. I write the "Agent State" to AlloyDB and pass the &lt;code&gt;transaction_id&lt;/code&gt; to the next agent.&lt;/p&gt;

&lt;p&gt;Here is how I structure the state transition in my FastAPI service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;fastapi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BackgroundTasks&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;sqlalchemy.ext.asyncio&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AsyncSession&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;AgentState&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;FastAPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_agent_workflow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;AsyncSession&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# Fetch current state from AlloyDB
&lt;/span&gt;    &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AgentState&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;AgentState&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;job_id&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;job_id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="c1"&gt;# Execute Gemini Agent
&lt;/span&gt;    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;gemini_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Analyze this JD: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jd_data&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gemini-3.5-flash&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Update state atomically
&lt;/span&gt;    &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TAILORING_COMPLETE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;db&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;commit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By using AlloyDB, I get the performance of PostgreSQL with the ability to store complex agent logs as JSONB. This allows me to query exactly where an agent failed—crucial when you're debugging 6 concurrent agents.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Gotchas
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The "Hallucination Buffer":&lt;/strong&gt; Even with Gemini 3.5, agents get creative. I implemented a strict schema validation step using Pydantic models. If the agent returns a JSON that doesn't match the schema, the pipeline halts immediately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concurrency Limits:&lt;/strong&gt; Running 6 agents at once will hit your Google Cloud rate limits faster than you think. I implemented a simple semaphore system in FastAPI to queue requests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Webpack/Frontend headaches:&lt;/strong&gt; While building the &lt;code&gt;switchwithai-frontend&lt;/code&gt; this week, I ran into a massive &lt;code&gt;js-yaml&lt;/code&gt; webpack error. The fix was adding a buffer polyfill. If you’re building AI tools, never underestimate the "glue code" required to make the frontend talk to your Python backend.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Why AlloyDB over standard Postgres?
&lt;/h3&gt;

&lt;p&gt;I chose AlloyDB because of its integration with the Google ecosystem. Since I’m already using Google ADK and Gemini, having the database managed within the same VPC reduces latency between my FastAPI instances and the data store. When you are processing high-volume job data, that 20ms-50ms latency saving adds up across hundreds of API calls.&lt;/p&gt;

&lt;h3&gt;
  
  
  Moving Forward
&lt;/h3&gt;

&lt;p&gt;My current focus is on closing the loop. I’m currently refining the &lt;code&gt;OCR Pipeline&lt;/code&gt; (using PaddleOCR v5) to handle document scanning for the job hunt engine, ensuring the resume ingestion is as accurate as the generation.&lt;/p&gt;

&lt;p&gt;This week, I also pushed updates to my &lt;code&gt;rishh-website&lt;/code&gt; repo—specifically cleaning up service account debugging—because managing credentials across 6 agents is a nightmare if you don't have a clean workflow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The takeaway:&lt;/strong&gt; Don't just build an agent. Build a system that tracks what the agent did. If you don't have a database layer that acts as the "brain's memory," you aren't building an AI application; you're building a prototype.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's your approach to managing agent state in production? Are you using a dedicated state machine or just relying on database rows? Drop it in the comments.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>fastapi</category>
      <category>googlecloud</category>
    </item>
  </channel>
</rss>
