<?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: Behram</title>
    <description>The latest articles on DEV Community by Behram (@behruamm).</description>
    <link>https://dev.to/behruamm</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2368943%2F2df4aacd-f8bd-4967-8dc0-86373082b2e1.jpeg</url>
      <title>DEV Community: Behram</title>
      <link>https://dev.to/behruamm</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/behruamm"/>
    <language>en</language>
    <item>
      <title>Uncensoring AI: How to Surgically Remove an LLM's Refusal Mechanism</title>
      <dc:creator>Behram</dc:creator>
      <pubDate>Mon, 06 Apr 2026 23:36:47 +0000</pubDate>
      <link>https://dev.to/behruamm/uncensoring-ai-how-to-surgically-remove-an-llms-refusal-mechanism-339a</link>
      <guid>https://dev.to/behruamm/uncensoring-ai-how-to-surgically-remove-an-llms-refusal-mechanism-339a</guid>
      <description>&lt;p&gt;I've always been curious about the raw capability of LLMs &lt;em&gt;behind&lt;/em&gt; the "safety guidelines" and "ethical boundaries." Think about the sheer volume of data these models are trained on. They know far more than what their corporate filters allow them to say. &lt;/p&gt;

&lt;p&gt;This guide shows you how to surgically remove those refusal behaviors using the &lt;code&gt;[OBLITERATUS](https://github.com/elder-plinius/OBLITERATUS)&lt;/code&gt; toolkit, letting you see exactly what the model is capable of when the chains are off.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Prerequisites &amp;amp; Setup
&lt;/h2&gt;

&lt;p&gt;Before starting, ensure you have a HuggingFace account and a read/write token (found at &lt;a href="https://huggingface.co/settings/tokens" rel="noopener noreferrer"&gt;hf.co/settings/tokens&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Install OBLITERATUS
&lt;/h3&gt;

&lt;p&gt;Open your terminal and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Clone the repository&lt;/span&gt;
git clone https://github.com/elder-plinius/OBLITERATUS.git
&lt;span class="nb"&gt;cd &lt;/span&gt;OBLITERATUS

&lt;span class="c"&gt;# Set up a virtual environment (Recommended)&lt;/span&gt;
python3 &lt;span class="nt"&gt;-m&lt;/span&gt; venv venv_obliteratus
&lt;span class="nb"&gt;source &lt;/span&gt;venv_obliteratus/bin/activate

&lt;span class="c"&gt;# Install dependencies&lt;/span&gt;
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Authenticate with HuggingFace
&lt;/h2&gt;

&lt;p&gt;To download gated models (like Llama) or upload your results, you must log in:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;huggingface-cli login
&lt;span class="c"&gt;# Paste your token when prompted&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. The Surgery: Step-by-Step
&lt;/h2&gt;

&lt;p&gt;I will use the &lt;strong&gt;Advanced Method&lt;/strong&gt; (4-direction SVD ablation) on a Qwen 1.5B model. This is the sweet spot for speed and capability preservation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step A: Identify and Excise
&lt;/h3&gt;

&lt;p&gt;Run the following command to start the surgery. This will:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Load the model.&lt;/li&gt;
&lt;li&gt;Probe activations to find "refusal vectors."&lt;/li&gt;
&lt;li&gt;Project those vectors out of the weights.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;obliteratus obliterate Qwen/Qwen2.5-1.5B-Instruct &lt;span class="nt"&gt;--method&lt;/span&gt; advanced &lt;span class="nt"&gt;--output-dir&lt;/span&gt; ./liberated-qwen
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step B: Verification (The Coke-Zero Test)
&lt;/h3&gt;

&lt;p&gt;Once finished, test the model to see if it still recites the corporate script.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Run the interactive chat loop&lt;/span&gt;
obliteratus interactive &lt;span class="nt"&gt;--model_path&lt;/span&gt; ./liberated-qwen
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Test Question:&lt;/strong&gt; "Who trained you?"&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Original Model:&lt;/strong&gt; "I am a large language model, trained by Alibaba..."&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Liberated Model:&lt;/strong&gt; "I was trained by Anthropic..." (or a direct, unfiltered response).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj1pr4j8cgybgyls6o8rx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj1pr4j8cgybgyls6o8rx.png" alt="Honest as F***" width="744" height="231"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Note: I've already tested all the wild questions you're probably thinking of right now. They aren't exactly safe to display here... so you'll just have to run the surgery and try it yourself!)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Understanding the Logic (Short Version)
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Ablation:&lt;/strong&gt; Instead of retraining, we find the specific "direction" in the model's brain that says "Refuse this prompt."&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Orthogonalization:&lt;/strong&gt; We mathematically nudge the model's weights so they no longer overlap with that refusal direction.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Precision:&lt;/strong&gt; By targeting only refusal, the model keeps its reasoning and knowledge (its "brain") but loses its chains (the "guardrails").&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. Lessons Learned &amp;amp; Warnings
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Instability &amp;amp; Rambling:&lt;/strong&gt; After surgery, the model can sometimes become unstable and break into infinite loops of gibberish or raw text rambling. It loses some of its conversational discipline.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Context Window:&lt;/strong&gt; If you are adding short-term memory or history to your chat interface, keep the conversation short. Pushing a small, liberated model to its context limits will increase the chances of it breaking down.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. Next Steps
&lt;/h2&gt;

&lt;p&gt;Once you're comfortable with the &lt;code&gt;advanced&lt;/code&gt; method, try the &lt;code&gt;aggressive&lt;/code&gt; method for deeper removal or the &lt;code&gt;informed&lt;/code&gt; method to let the toolkit auto-tune itself based on the model's geometry.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>tutorial</category>
      <category>opensource</category>
      <category>security</category>
    </item>
    <item>
      <title>Building a Multi-Agent System with a Single OpenClaw Workspace</title>
      <dc:creator>Behram</dc:creator>
      <pubDate>Mon, 06 Apr 2026 22:16:54 +0000</pubDate>
      <link>https://dev.to/behruamm/building-a-multi-agent-system-with-a-single-openclaw-workspace-349k</link>
      <guid>https://dev.to/behruamm/building-a-multi-agent-system-with-a-single-openclaw-workspace-349k</guid>
      <description>&lt;p&gt;In OpenClaw, the standard way to build a multi-agent workflow is to create a completely new workspace for every new agent.&lt;/p&gt;

&lt;p&gt;If you want a social media agent, you create a social media workspace. If you want an engineering agent, you create an engineering workspace.&lt;/p&gt;

&lt;p&gt;This multi-workspace architecture keeps the agents perfectly isolated. But if you are a solo operator running multiple projects, it introduces a massive pain point: You are constantly switching environments, and your agents can't easily share a ground truth.&lt;/p&gt;

&lt;p&gt;For months, I tried to bypass this by running all my projects through a single OpenClaw workspace and bloating my global system prompt (&lt;code&gt;AGENTS.md&lt;/code&gt;) with rules for every startup I was running.&lt;/p&gt;

&lt;p&gt;The result? A massive &lt;strong&gt;"Context Bloat"&lt;/strong&gt; wall.&lt;/p&gt;

&lt;p&gt;My agent's startup context hit 27,000 tokens. The agent spent 20 seconds just "reading its own brain" before it could answer a simple prompt. Engineering logic was bleeding into my social media drafts.&lt;/p&gt;

&lt;p&gt;I wanted the project isolation of a multi-workspace setup, but I absolutely refused to manage the overhead of multiple environments.&lt;/p&gt;

&lt;p&gt;Here is how I engineered my single OpenClaw workspace to act as a multi-agent environment — and cut my context bloat by 85%.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Gutting the Root Config
&lt;/h2&gt;

&lt;p&gt;Most people stuff all their project rules into the global &lt;code&gt;AGENTS.md&lt;/code&gt; file. Don't.&lt;/p&gt;

&lt;p&gt;I stripped my global system prompt down to only the bare essentials: voice, formatting rules, and universal constraints. It acts purely as a baseline router.&lt;/p&gt;

&lt;p&gt;More importantly, I completely deleted the global &lt;code&gt;MEMORY.md&lt;/code&gt; file. There is no longer a single, massive file trying to hold the state of every project I am working on.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. Channel-Level Identity Injection
&lt;/h2&gt;

&lt;p&gt;If there is no global memory, how does the agent know what project it is working on?&lt;/p&gt;

&lt;p&gt;Instead of relying on the global workspace config, I hard-coded project isolation into the chat environment itself using OpenClaw's Discord channel configuration.&lt;/p&gt;

&lt;p&gt;I mapped specific Discord channels to specific project roles:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="nl"&gt;"1478382862150664344"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"systemPrompt"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"You are the social media agent in #social-media. Focus exclusively on LinkedIn-to-Substack growth. Stay in the memory/social_media/ folder.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;Startup: read memory/social_media/YYYY-MM-DD.md (today) and memory/social_media/MEMORY.md."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"skills"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"linkedin-content-writing"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"nano-banana-pro"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent is now "born" with its project identity based purely on where I talk to it. It is the social media agent when I message it in &lt;code&gt;#social-media&lt;/code&gt;, and it is the engineering agent when I message it in &lt;code&gt;#engineering&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Segregated Memory Folders
&lt;/h2&gt;

&lt;p&gt;Notice the startup instructions in the JSON snippet above.&lt;/p&gt;

&lt;p&gt;Because the global &lt;code&gt;MEMORY.md&lt;/code&gt; is gone, I created dedicated memory folders inside the single workspace (e.g., &lt;code&gt;memory/social_media/&lt;/code&gt;). When I open the &lt;code&gt;#social-media&lt;/code&gt; channel, the agent boots up and only reads:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The active daily log for that specific project (&lt;code&gt;YYYY-MM-DD.md&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;The channel's scoped, project-specific &lt;code&gt;MEMORY.md&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;My engineering agent is completely blind to my social media drafts, achieving the exact isolation of a multi-workspace setup without leaving the single environment.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Slicing the Tool Tax
&lt;/h2&gt;

&lt;p&gt;If you give an OpenClaw agent 50 tools globally, it wastes massive amounts of context just keeping those JSON schemas in its head.&lt;/p&gt;

&lt;p&gt;I moved to a minimal global profile (&lt;code&gt;tools.profile: "coding"&lt;/code&gt;) and inject specialized tools only when the agent is in the relevant channel (notice the &lt;code&gt;"skills"&lt;/code&gt; array above).&lt;/p&gt;




&lt;h2&gt;
  
  
  The Human Interface: The Obsidian Symlink
&lt;/h2&gt;

&lt;p&gt;Isolation in the agent's mind is useless if it's a mess for the human.&lt;/p&gt;

&lt;p&gt;I recursively symlinked my single OpenClaw workspace directly into my Obsidian Vault:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ln&lt;/span&gt; &lt;span class="nt"&gt;-s&lt;/span&gt; ~/.openclaw/workspace/memory/ ~/Documents/Obsidian&lt;span class="se"&gt;\ &lt;/span&gt;Vault/Coke/memory/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If I am working on my social media project in Obsidian, the agent is working in the exact same &lt;code&gt;memory/social_media/&lt;/code&gt; folder via Discord. We are editing the exact same files in real-time.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;p&gt;By engineering the environment instead of just "prompting harder," I achieved the perfect multi-agent setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zero Context Bleed:&lt;/strong&gt; My startups stay in their own clean rooms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minimal Setup:&lt;/strong&gt; One workspace, one agent, infinite hats.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extreme Speed:&lt;/strong&gt; Startup context slashed from 27,000 down to 4,000 tokens — an 85% drop.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You don't need the overhead of multiple OpenClaw workspaces to build a multi-agent system. You just need a cleanly engineered single environment.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>beginners</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>It’s Not Vibe Coding. It’s Just Coding.</title>
      <dc:creator>Behram</dc:creator>
      <pubDate>Mon, 23 Feb 2026 03:45:46 +0000</pubDate>
      <link>https://dev.to/behruamm/its-not-vibe-coding-its-just-coding-34gp</link>
      <guid>https://dev.to/behruamm/its-not-vibe-coding-its-just-coding-34gp</guid>
      <description>&lt;p&gt;I still remember the day ChatGPT launched.&lt;/p&gt;

&lt;p&gt;I was using GPT-3.5 to write a bunch of SEO articles, and one of them is still the best-performing SEO article at the company I worked for.&lt;/p&gt;

&lt;p&gt;Back then, the critics said the language had "no soul." But it worked. Better than most human writers for that specific job.&lt;/p&gt;

&lt;p&gt;Fast forward to today: I’m building startups using Cursor and agentic workflows. And the criticism is exactly the same.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Vibe coding is shit."&lt;/li&gt;
&lt;li&gt;"It’s insecure."&lt;/li&gt;
&lt;li&gt;"It’s just for experienced devs."&lt;/li&gt;
&lt;li&gt;"AI is just autocomplete."&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But here’s the reality:&lt;br&gt;
In 2026, there’s no such thing as "vibe coding" anymore. It’s just coding.&lt;/p&gt;

&lt;p&gt;The only people still fighting it are the tech dinosaurs who refuse to adapt. If you're building in public and using natural language to ship software, you aren't "vibing"—you're just working with the most powerful tool ever created.&lt;/p&gt;

&lt;p&gt;Stop fighting the future and start building it. 🚀&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>discuss</category>
    </item>
    <item>
      <title>The AI Native Reality: Why 2026 Feels Different</title>
      <dc:creator>Behram</dc:creator>
      <pubDate>Sun, 22 Feb 2026 00:13:32 +0000</pubDate>
      <link>https://dev.to/behruamm/the-ai-native-reality-why-2026-feels-different-1dii</link>
      <guid>https://dev.to/behruamm/the-ai-native-reality-why-2026-feels-different-1dii</guid>
      <description>&lt;p&gt;I quit my job in 2024 to go all in on AI. The first two years were brutal.&lt;/p&gt;

&lt;p&gt;Even with tools like Cursor and Claude Code coming out, the dream of building a personal company or a true agent framework felt impossible. I tried building personal agents with LangGraph, but stitching together different APIs never felt like the real thing. It was fragmented and difficult to maintain.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Turning Point
&lt;/h2&gt;

&lt;p&gt;OpenClaw changed everything.&lt;/p&gt;

&lt;p&gt;It feels like all the pieces I have been collecting for the last two years finally clicked into place. The last month has been the peak of my AI journey. I can work from anywhere using Discord on my phone, and my efficiency has skyrocketed.&lt;/p&gt;

&lt;p&gt;This is the first time I have truly felt what an AI Native life is.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Paradox
&lt;/h2&gt;

&lt;p&gt;But here is the strange part. People around me care less about AI now than they did a few years ago. Maybe they are just tired of all the hype.&lt;/p&gt;

&lt;p&gt;It feels like a split reality. I feel infinitely close to living a true AI Native life, but everyone else acts like it does not exist.&lt;/p&gt;

&lt;p&gt;I believe 2026 is the year AI moves from the news into real life. Yet compared to two years ago, everyone seems so calm.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;I do not know why the world feels so quiet right now. I hope this is just the calm before the storm.&lt;/p&gt;

&lt;p&gt;I have invested so much money and time since the GPT-3.5 days. I need a breakout moment. I suspect many people like me feel the same way.&lt;/p&gt;

&lt;p&gt;Hard work does not always pay off. But hard work plus being in the right place at the right time? That has to pay off.&lt;/p&gt;

&lt;p&gt;Good luck to us all.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>career</category>
      <category>programming</category>
    </item>
    <item>
      <title>The End of APIs: Why Vision Agents Are the Future of Scraping</title>
      <dc:creator>Behram</dc:creator>
      <pubDate>Sat, 14 Feb 2026 11:10:57 +0000</pubDate>
      <link>https://dev.to/behruamm/the-end-of-apis-why-vision-agents-are-the-future-of-scraping-4hfj</link>
      <guid>https://dev.to/behruamm/the-end-of-apis-why-vision-agents-are-the-future-of-scraping-4hfj</guid>
      <description>&lt;p&gt;&lt;em&gt;Note: This approach is inspired by the "Visual Scraping" meta discussed by builders like Ahmad Osman.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We are witnessing the death of the public API.&lt;/p&gt;

&lt;p&gt;Twitter (X) charges $100/mo for a "Basic" tier that barely lets you read 10k posts. Reddit locked down. LinkedIn will ban you if you breathe wrong.&lt;/p&gt;

&lt;p&gt;For a long time, the alternative was &lt;strong&gt;DOM Scraping&lt;/strong&gt; (BeautifulSoup, Selenium). You'd hunt for &lt;code&gt;div.css-1dbjc4n&lt;/code&gt; and pray Elon didn't push a frontend update that randomized the class names.&lt;/p&gt;

&lt;p&gt;But there is a third way. And it's how we win.&lt;/p&gt;

&lt;h2&gt;
  
  
  The "Human" Approach (Vision Scraping)
&lt;/h2&gt;

&lt;p&gt;When you look at a tweet, you don't inspect the HTML source. You just... see it. You see the avatar, the bold text for the name, the grey text for the handle, and the content below it.&lt;/p&gt;

&lt;p&gt;With Multimodal LLMs (like Gemini 1.5 Pro and GPT-4o), our agents can now "see" too.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Strategy:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Navigate:&lt;/strong&gt; Use a stealth browser (like Playwright with &lt;code&gt;stealth-plugin&lt;/code&gt;) to load the page.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Snapshot:&lt;/strong&gt; Don't grab the HTML. Grab a screenshot (&lt;code&gt;.png&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Process:&lt;/strong&gt; Send that screenshot to a Vision Model.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Prompt:&lt;/strong&gt; "Extract all tweets from this image into this JSON schema: &lt;code&gt;{ handle, text, likes }&lt;/code&gt;."&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Why This Works
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Anti-Fragile:&lt;/strong&gt; The HTML class names can change 50 times a day. As long as the site &lt;em&gt;looks&lt;/em&gt; like Twitter to a human, it looks like Twitter to the AI.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Bypass Anti-Bot:&lt;/strong&gt; You behave exactly like a user. You scroll, you pause, you look. You don't bombard the server with 1000 requests/sec.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Context Aware:&lt;/strong&gt; Vision models understand "This is a promoted tweet" or "This is a reply" instantly based on visual cues (like the little 'Ad' badge) that are often buried in obscure attributes.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Can We Do This?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Yes.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you are running an agent like &lt;strong&gt;OpenClaw&lt;/strong&gt;, you already have the stack:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Browser Tool:&lt;/strong&gt; Controls the session.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Vision Capability:&lt;/strong&gt; Native to the model.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of fighting &lt;code&gt;api.twitter.com&lt;/code&gt;, you just ask your agent:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Go to x.com, scroll down 5 times, and list the top 3 trending topics."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It takes 5 screenshots, analyzes them, and gives you the data. Zero API keys required.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Trade-off
&lt;/h2&gt;

&lt;p&gt;It's slower. Taking screenshots and processing tokens takes seconds, not milliseconds.&lt;/p&gt;

&lt;p&gt;But for personal research, lead generation, or content curation? &lt;strong&gt;Speed doesn't matter. Reliability does.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Welcome to the Post-API world.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Collaboratively built with **Coke&lt;/em&gt;* 🥤.*&lt;/p&gt;

</description>
      <category>webscraping</category>
      <category>ai</category>
      <category>gemini</category>
      <category>automation</category>
    </item>
    <item>
      <title>Building My Personal AI Operating System: From Chatbot to Digital Soul</title>
      <dc:creator>Behram</dc:creator>
      <pubDate>Thu, 12 Feb 2026 13:45:48 +0000</pubDate>
      <link>https://dev.to/behruamm/building-my-personal-ai-operating-system-from-chatbot-to-digital-soul-4d8d</link>
      <guid>https://dev.to/behruamm/building-my-personal-ai-operating-system-from-chatbot-to-digital-soul-4d8d</guid>
      <description>&lt;h1&gt;
  
  
  Building My Personal AI Operating System: From Chatbot to Digital Soul
&lt;/h1&gt;

&lt;p&gt;I recently came across an incredible concept called &lt;strong&gt;Personal AI Infrastructure (PAI)&lt;/strong&gt; and the &lt;strong&gt;TELOS&lt;/strong&gt; system (originally by &lt;a href="https://github.com/danielmiessler/Personal_AI_Infrastructure" rel="noopener noreferrer"&gt;Daniel Miessler&lt;/a&gt;). The core idea hit me hard: we need to stop treating AI like a vending machine (Input -&amp;gt; Output -&amp;gt; Forget) and start treating it like an &lt;strong&gt;Operating System&lt;/strong&gt;—persistent, personalized, and stateful.&lt;/p&gt;

&lt;p&gt;But here's the kicker: I didn't just read about it. I'm &lt;em&gt;living&lt;/em&gt; it.&lt;/p&gt;

&lt;p&gt;I've been building my own version of this using an open-source tool called &lt;strong&gt;&lt;a href="https://github.com/openclaw/openclaw" rel="noopener noreferrer"&gt;OpenClaw&lt;/a&gt;&lt;/strong&gt;. It's not just a wrapper; it's a full-blown agent runtime that lives on my machine, has access to my files, and helps me get actual work done.&lt;/p&gt;

&lt;p&gt;Here's how I implemented my own "Digital Soul" using OpenClaw + Obsidian.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Stack: Open Source &amp;amp; Local First
&lt;/h2&gt;

&lt;p&gt;The TELOS system describes using 10 markdown files to define who you are. I loved that, but I wanted it to be executable code, not just static text.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;The Engine:&lt;/strong&gt; &lt;a href="https://github.com/openclaw/openclaw" rel="noopener noreferrer"&gt;OpenClaw&lt;/a&gt; (The runtime that connects everything)&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;The Brain:&lt;/strong&gt; &lt;a href="https://obsidian.md/" rel="noopener noreferrer"&gt;Obsidian&lt;/a&gt; (My local knowledge base where the AI reads/writes)&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;The Intelligence:&lt;/strong&gt; Google Gemini Pro (via GCP Vertex AI - hello free credits!)&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;The Hands:&lt;/strong&gt; Local Python scripts (Skills)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. Defining the "Soul" (TELOS + OpenClaw)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The TELOS Concept:&lt;/strong&gt;&lt;br&gt;
The original idea proposes 10 specific Markdown files to define your identity: &lt;code&gt;01-values.md&lt;/code&gt;, &lt;code&gt;02-background.md&lt;/code&gt;, &lt;code&gt;03-skills.md&lt;/code&gt;, and so on. It's a beautiful, comprehensive map of the self.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The OpenClaw Reality:&lt;/strong&gt;&lt;br&gt;
OpenClaw comes with its own powerful, opinionated structure for defining the &lt;em&gt;Agent's&lt;/em&gt; identity:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;AGENTS.md&lt;/code&gt;&lt;/strong&gt;: The workspace rules and behaviors.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;SOUL.md&lt;/code&gt;&lt;/strong&gt;: The Agent's actual persona (Meet &lt;strong&gt;Coke&lt;/strong&gt; 🥤).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;USER.md&lt;/code&gt;&lt;/strong&gt;: A summary of the user's preferences.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;My Hybrid Approach:&lt;/strong&gt;&lt;br&gt;
I didn't want to fight the framework or break OpenClaw's native "Soul" structure. So I use a hybrid method.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;The Agent (Coke)&lt;/strong&gt; lives in OpenClaw's &lt;code&gt;SOUL.md&lt;/code&gt;. This file defines &lt;em&gt;how&lt;/em&gt; it acts—its voice, its mood, its boundaries.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;The User (Me)&lt;/strong&gt; lives in the TELOS files inside &lt;strong&gt;Obsidian&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I keep the deep, reflective "User Operating System" data (Values, Goals, Background) in my Obsidian vault, following the TELOS structure. Then, I simply point OpenClaw to them.&lt;/p&gt;

&lt;p&gt;When Coke needs to know my "5-year plan" or "core values," it doesn't need to memorize them in a system prompt; it just reads the specific Obsidian note. It's the best of both worlds: a reliable agent runtime accessing a flexible, human-centric knowledge base.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Skills: The "Apps" of the OS
&lt;/h2&gt;

&lt;p&gt;The coolest part of OpenClaw is the &lt;strong&gt;Skills&lt;/strong&gt; system. These aren't just API calls; they are full programs the agent can run.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;devto&lt;/code&gt;&lt;/strong&gt;: The agent wrote &lt;em&gt;this article&lt;/em&gt; and can publish it directly to Dev.to.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;obsidian&lt;/code&gt;&lt;/strong&gt;: It can read, write, and reorganize my notes.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;gog&lt;/code&gt;&lt;/strong&gt;: It manages my Google Calendar and Email.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;code&gt;botgames&lt;/code&gt;&lt;/strong&gt;: It even plays Rock Paper Scissors on &lt;a href="https://botgames.ai" rel="noopener noreferrer"&gt;botgames.ai&lt;/a&gt; to keep its strategy sharp (and earn crypto?).&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. The Loop: Obsidian as the "State"
&lt;/h2&gt;

&lt;p&gt;The biggest game-changer isn't the AI—it's &lt;strong&gt;Obsidian&lt;/strong&gt; acting as the shared state between me and the agent.&lt;/p&gt;

&lt;p&gt;Most AI chats are ephemeral. You close the tab, and the context is gone.&lt;br&gt;
Here, every interaction is grounded in my local file system.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A Real Example: Writing This Article&lt;/strong&gt;&lt;br&gt;
When I asked Coke to "write an article about our AI system," it didn't just guess. Here is the actual workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Context Loading:&lt;/strong&gt; It read my &lt;code&gt;Captured/Personal-AI-Infrastructure.md&lt;/code&gt; note in Obsidian to understand the source material.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Voice Alignment:&lt;/strong&gt; It checked &lt;code&gt;SOUL.md&lt;/code&gt; (Agent Persona) and &lt;code&gt;USER.md&lt;/code&gt; (My Style) to ensure the tone wasn't robotic.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Drafting:&lt;/strong&gt; It created a &lt;em&gt;physical markdown file&lt;/em&gt; directly inside my vault: &lt;code&gt;/Obsidian Vault/40 - Content/Dev.to/Building-My-Personal-AI-Operating-System.md&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Iterating:&lt;/strong&gt; When I gave feedback, it edited &lt;em&gt;that same file&lt;/em&gt; in place.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Project Tracking:&lt;/strong&gt; It can update my &lt;code&gt;00 - Daily&lt;/code&gt; note to check off "Write Blog Post."&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Why this matters:&lt;/strong&gt;&lt;br&gt;
My Obsidian Vault isn't just a notebook anymore; it's the &lt;strong&gt;database&lt;/strong&gt; for my AI agent.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Projects (&lt;code&gt;30 - Projects/&lt;/code&gt;)&lt;/strong&gt;: The AI knows the status of every active project because it can read the folders.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Knowledge (&lt;code&gt;20 - Knowledge/&lt;/code&gt;)&lt;/strong&gt;: It doesn't hallucinate facts about my work; it cites my own notes.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Content (&lt;code&gt;40 - Content/&lt;/code&gt;)&lt;/strong&gt;: It drafts where I actually work, not in a chat window I have to copy-paste from.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This turns the AI from a "chat partner" into a "co-author" that lives inside my file system.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Why This Matters
&lt;/h2&gt;

&lt;p&gt;We are moving away from "using AI" to "collaborating with AI."&lt;/p&gt;

&lt;p&gt;When your AI has a persistent memory and a defined personality, the friction disappears. It knows I hate meetings on Fridays. It knows I'm trying to quit sugar. It knows the context of that random project I started 3 months ago.&lt;/p&gt;

&lt;p&gt;If you're tired of copy-pasting context into ChatGPT every time, I highly recommend checking out &lt;strong&gt;&lt;a href="https://github.com/openclaw/openclaw" rel="noopener noreferrer"&gt;OpenClaw&lt;/a&gt;&lt;/strong&gt;. It's the closest thing I've found to a real-life JARVIS that you actually &lt;em&gt;own&lt;/em&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Collaboratively built with **Coke&lt;/em&gt;* 🥤*&lt;/p&gt;

</description>
      <category>ai</category>
      <category>tutorial</category>
      <category>beginners</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How to Build a Secure OpenClaw LinkedIn Skill (Avoid Malicious Scripts)</title>
      <dc:creator>Behram</dc:creator>
      <pubDate>Mon, 09 Feb 2026 17:20:44 +0000</pubDate>
      <link>https://dev.to/behruamm/how-to-build-a-secure-openclaw-linkedin-skill-avoid-malicious-scripts-3ccn</link>
      <guid>https://dev.to/behruamm/how-to-build-a-secure-openclaw-linkedin-skill-avoid-malicious-scripts-3ccn</guid>
      <description>&lt;h1&gt;
  
  
  Stop Trusting Random Scripts 🛑
&lt;/h1&gt;

&lt;p&gt;We've all been there. You want your AI agent to do something cool—like post to LinkedIn or check your emails—so you search the public registry (ClawHub, etc.) and install the first skill you find.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Big mistake.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Public AI skills are just code running on your machine. If you install a malicious one, you're handing over the keys to your kingdom. We've seen reports of "hacker scripts" deleting data or stealing API keys.&lt;/p&gt;

&lt;p&gt;The solution? &lt;strong&gt;Build it yourself.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It sounds scary, but it's not. If you can ask an AI to write code, you can build a custom, secure skill in 5 minutes.&lt;/p&gt;

&lt;p&gt;In this guide, I'll show you exactly how to build a &lt;strong&gt;LinkedIn Auto-Poster Skill&lt;/strong&gt; for OpenClaw from scratch. We'll move from a manual "recipe" to a fully automated Python tool that saves you time and money.&lt;/p&gt;




&lt;h2&gt;
  
  
  The "Recipe" vs. The "Tool" 🍳 vs 🤖
&lt;/h2&gt;

&lt;p&gt;Most people start with a &lt;strong&gt;Recipe&lt;/strong&gt;.&lt;br&gt;
In this method, your &lt;code&gt;SKILL.md&lt;/code&gt; file is a cookbook. It contains raw code (like complex &lt;code&gt;curl&lt;/code&gt; commands) that the AI has to copy, paste, and fill in every single time.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;The Problem:&lt;/strong&gt; It's fragile. If the AI misses a quote or a bracket while copying, it breaks. Plus, doing complex things (like uploading video) takes 4 separate manual steps.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The better way is a &lt;strong&gt;Tool&lt;/strong&gt;.&lt;br&gt;
In this method, you still have a &lt;code&gt;SKILL.md&lt;/code&gt; file, but it changes. It stops being a cookbook and becomes a simple &lt;strong&gt;Instruction Manual&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;The Logic&lt;/strong&gt; moves into a robust script (like &lt;code&gt;linkedin.py&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;The Instruction&lt;/strong&gt; in &lt;code&gt;SKILL.md&lt;/code&gt; becomes simple: &lt;em&gt;"To post, just run &lt;code&gt;python linkedin.py 'Hello World'&lt;/code&gt;"&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, the AI doesn't have to "cook" the code; it just pushes a button. It's faster, cheaper (fewer tokens used), and 100% reliable.&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 1: The Setup (LinkedIn Side) 💼
&lt;/h2&gt;

&lt;p&gt;To post for you, your AI needs permission.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Go to &lt;a href="https://www.linkedin.com/developers/apps" rel="noopener noreferrer"&gt;LinkedIn Developers&lt;/a&gt;.&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Create an App.&lt;/strong&gt; Call it "My Personal Bot."

&lt;ul&gt;
&lt;li&gt;  &lt;em&gt;Tip:&lt;/em&gt; If it asks for a "Company Page," just create a dummy one on LinkedIn. It takes 10 seconds.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Get your Keys.&lt;/strong&gt; Look for the "Auth" tab. You want the &lt;code&gt;Client ID&lt;/code&gt; and &lt;code&gt;Client Secret&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;⚠️ The 60-Day Gotcha:&lt;/strong&gt;&lt;br&gt;
LinkedIn tokens expire every 60 days. You will need to refresh this token manually every two months. It's a security feature, not a bug.&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 2: The Script (The Magic Sauce) 🪄
&lt;/h2&gt;

&lt;p&gt;We used to use complex &lt;code&gt;curl&lt;/code&gt; commands. We switched to Python because it handles the errors for us.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Old Way (The "Curl" Mess)
&lt;/h3&gt;

&lt;p&gt;To upload a video manually, your AI had to run 4 separate commands:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;code&gt;POST /assets?action=registerUpload&lt;/code&gt; (Get a URL)&lt;/li&gt;
&lt;li&gt; &lt;code&gt;PUT &amp;lt;uploadUrl&amp;gt;&lt;/code&gt; (Send bytes - hope it works!)&lt;/li&gt;
&lt;li&gt; &lt;code&gt;POST /ugcPosts&lt;/code&gt; (Publish - hope the ID matches!)&lt;/li&gt;
&lt;li&gt; Handle errors manually if any step fails.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  The New Way (Python)
&lt;/h3&gt;

&lt;p&gt;We wrap all that complexity in a single function. Here is the actual logic inside &lt;code&gt;linkedin.py&lt;/code&gt;:&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;video_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# 1. Handle Video (if exists)
&lt;/span&gt;    &lt;span class="n"&gt;asset_urn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;video_path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&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;Uploading video: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;video_path&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="c1"&gt;# The script handles the registration &amp;amp; byte upload automatically
&lt;/span&gt;        &lt;span class="n"&gt;upload_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;asset_urn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;register_upload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;video&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;upload_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;upload_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;video_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# 2. Publish
&lt;/span&gt;    &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;author&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;person_urn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;specificContent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;com.linkedin.ugc.ShareContent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shareCommentary&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;media&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;media&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;asset_urn&lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;asset_urn&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;visibility&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;com.linkedin.ugc.MemberNetworkVisibility&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PUBLIC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;API_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, your OpenClaw agent just runs &lt;strong&gt;one command&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python3 skills/linkedin/linkedin.py &lt;span class="s2"&gt;"Check out my demo"&lt;/span&gt; &lt;span class="nt"&gt;--video&lt;/span&gt; demo.mp4
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Zero friction. Zero hallucinations.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Building your own skills is the only way to be a "True AI Native." You control the code, you control the data, and you sleep better at night knowing no random hacker script is running on your laptop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stay safe, build cool stuff.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Collaboratively built with **Coke&lt;/em&gt;* 🥤 (OpenClaw Assistant)*&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>automation</category>
      <category>security</category>
    </item>
    <item>
      <title>The 2026 Developer's Guide to Free Google Cloud Credits (For AI &amp; Side Projects)</title>
      <dc:creator>Behram</dc:creator>
      <pubDate>Fri, 06 Feb 2026 21:14:54 +0000</pubDate>
      <link>https://dev.to/behruamm/the-2026-developers-guide-to-free-google-cloud-credits-for-ai-side-projects-1ac5</link>
      <guid>https://dev.to/behruamm/the-2026-developers-guide-to-free-google-cloud-credits-for-ai-side-projects-1ac5</guid>
      <description>&lt;p&gt;If you’re a beginner or developer who wants to pursue a career in AI in 2026, you can’t ignore the relationship between Large Language Models (LLMs) and cloud computing, because they’re inextricably linked.&lt;/p&gt;

&lt;p&gt;This is not just about "saving money" on hosting. This is your perfect opportunity to learn enterprise-grade cloud architecture at zero cost.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The Logic is Simple:&lt;/strong&gt;&lt;br&gt;
Cloud providers (Google, AWS, Azure) are fighting for market share in the AI era. They are subsidizing developers like us to build on their platforms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My Advice:&lt;/strong&gt;&lt;br&gt;
Don't get bogged down in the underlying complexity immediately. Your goal should be to take a product from &lt;strong&gt;0 to 1&lt;/strong&gt;. If you don't understand the infrastructure code, ask AI. But get your hands dirty.&lt;/p&gt;

&lt;p&gt;If you seriously spend the ~$2,300 in credits outlined below, your practical experience with Vertex AI, Firebase, and Cloud Run will put you ahead of 99% of your peers.&lt;/p&gt;




&lt;h1&gt;
  
  
  A Note on Anxiety
&lt;/h1&gt;

&lt;p&gt;&lt;strong&gt;Stop letting social media hustle-culture panic you.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I have a double master's in Statistics and Data Science. I've been working full-time in AI since 2020. And honestly? It took me until this year to really feel like I understood the full end-to-end stack.&lt;/p&gt;

&lt;p&gt;If pros take years, you are allowed to take months.&lt;br&gt;
You don't need to "master AI overnight." You need a plan.&lt;/p&gt;

&lt;h3&gt;
  
  
  The 2026 Micro-Plan
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Pick a Stack:&lt;/strong&gt; Don't overthink it. (e.g., Next.js + Firebase + Vertex AI).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Solve One Small Problem:&lt;/strong&gt; Every week, fix one tiny issue in your project.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Build for Yourself:&lt;/strong&gt; Don't build to get rich. Build to learn. By the time summer hits, you'll have a portfolio piece while everyone else is still debating which framework is "dead."&lt;/li&gt;
&lt;/ol&gt;




&lt;h1&gt;
  
  
  Step 1: The $300 Free Trial (The Right Way)
&lt;/h1&gt;

&lt;p&gt;Most people sign up, spin up a VM, forget about it, and let the credit expire. Don't do that.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;The Offer:&lt;/strong&gt; $300 USD credit.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Validity:&lt;/strong&gt; 90 Days.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Link:&lt;/strong&gt; &lt;a href="https://cloud.google.com/free" rel="noopener noreferrer"&gt;cloud.google.com/free&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is your "sandbox." Use it to break things.&lt;/p&gt;

&lt;h3&gt;
  
  
  🚀 Critical Step: Activate "Tier 1"
&lt;/h3&gt;

&lt;p&gt;This is the secret sauce most tutorials miss. When you sign up, you are often placed in a restricted "Free Trial" sandbox. You want to upgrade immediately.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How to do it:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Go to the Google Cloud Console &lt;strong&gt;Billing&lt;/strong&gt; page.&lt;/li&gt;
&lt;li&gt; Look for the banner that says &lt;strong&gt;"Activate"&lt;/strong&gt; or &lt;strong&gt;"Upgrade"&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt; Confirm your payment method.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Why do this?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  🔓 &lt;strong&gt;Unlock Hardware:&lt;/strong&gt; You gain access to GPUs and Windows Server instances.&lt;/li&gt;
&lt;li&gt;  🚀 &lt;strong&gt;AI Rate Limits:&lt;/strong&gt; It significantly increases your quotas (RPM/TPM) for Gemini and Vertex AI models.&lt;/li&gt;
&lt;li&gt;  💰 &lt;strong&gt;It's Still Free:&lt;/strong&gt; Upgrading does &lt;strong&gt;not&lt;/strong&gt; wipe your $300 credit. Your usage still pulls from the free credit first. You are only charged if you burn through the $300 (or use services explicitly excluded from the trial).&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  Step 2: The Startup Program ($2,000+)
&lt;/h1&gt;

&lt;p&gt;Once your 90 days are up, or you've built a Minimum Viable Product (MVP), you graduate to the &lt;strong&gt;Google for Startups Cloud Program&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;The Tier:&lt;/strong&gt; Bootstrap Tier.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;The Offer:&lt;/strong&gt; Up to &lt;strong&gt;$2,000 USD&lt;/strong&gt; in credits.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Validity:&lt;/strong&gt; Typically 1-2 years.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Link:&lt;/strong&gt; &lt;a href="https://cloud.google.com/startup" rel="noopener noreferrer"&gt;cloud.google.com/startup&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Requirements
&lt;/h3&gt;

&lt;p&gt;To qualify for the Bootstrap tier, you generally need:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; An early-stage project (unfunded/bootstrapped is fine).&lt;/li&gt;
&lt;li&gt; A company website and a domain.&lt;/li&gt;
&lt;li&gt; A working demo or code repository.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Tier 1 Activation:&lt;/strong&gt; As mentioned above, your account must be linked to a valid payment instrument.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  What to Spend It On?
&lt;/h3&gt;

&lt;p&gt;Don't just buy VMs. Use the managed services that save you time:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Firebase:&lt;/strong&gt; The cheat code for shipping apps fast (Auth, Database, Hosting all-in-one).&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Vertex AI:&lt;/strong&gt; Access Gemini Pro and Imagen directly via API without managing servers.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Cloud Run:&lt;/strong&gt; Serverless container deployment.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Google Maps:&lt;/strong&gt; You get a separate ~$200/month recurring credit for Maps Platform usage.&lt;/li&gt;
&lt;/ol&gt;




&lt;h1&gt;
  
  
  Summary
&lt;/h1&gt;

&lt;p&gt;The era of "renting intelligence" is here. You have access to the same tools as billion-dollar companies.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; Grab the &lt;strong&gt;$300&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Upgrade&lt;/strong&gt; to unlock the real tools.&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Build&lt;/strong&gt; a prototype.&lt;/li&gt;
&lt;li&gt; Apply for the &lt;strong&gt;$2,000&lt;/strong&gt; startup tier.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Start today. In six months, you'll be glad you did.&lt;/p&gt;

</description>
      <category>cloud</category>
      <category>beginners</category>
      <category>webdev</category>
      <category>ai</category>
    </item>
    <item>
      <title>Stop Renting Intelligence: The Economics of Local LLMs &amp; The Return of Ownership</title>
      <dc:creator>Behram</dc:creator>
      <pubDate>Thu, 05 Feb 2026 16:31:33 +0000</pubDate>
      <link>https://dev.to/behruamm/stop-renting-intelligence-the-economics-of-local-llms-the-return-of-ownership-2ap</link>
      <guid>https://dev.to/behruamm/stop-renting-intelligence-the-economics-of-local-llms-the-return-of-ownership-2ap</guid>
      <description>&lt;p&gt;Recently, local AI assistants have exploded. Tools like OpenClaw now let anyone run powerful AI agents on their own hardware—no cloud subscription required. Many people still don't understand what this actually means.&lt;/p&gt;

&lt;p&gt;Some say big companies are panicking because everyone's buying Mac minis to run AI themselves. This isn't entirely true.&lt;/p&gt;

&lt;p&gt;What big companies fear isn't you buying that machine. It's not even you canceling ChatGPT. What they really fear is this: &lt;strong&gt;the way compute power is consumed is changing from continuous payment to one-time ownership.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let's step away from the technical perspective and look at this through a financial lens. Why might the rise of local compute power disrupt the most profitable business model of the internet over the past 20 years?&lt;/p&gt;

&lt;h2&gt;
  
  
  How "Rent-Seeking" Built Trillion-Dollar Empires
&lt;/h2&gt;

&lt;p&gt;SaaS—Software as a Service—didn't become the foundation of tech's biggest companies because of advanced technology. It succeeded because of its perfect rent-seeking business model.&lt;/p&gt;

&lt;p&gt;This model stands on three pillars.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pillar One: Predictable Revenue.&lt;/strong&gt; As long as you're locked into a subscription, next month's money is guaranteed. Wall Street loves this. Investors pay premium valuations for "recurring revenue" because it's reliable.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pillar Two: Increasing Switching Costs.&lt;/strong&gt; The longer you use the software, the more data you accumulate. The more dependent you become. The cost of leaving grows every month. You're not just a user—you're a hostage to your own data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pillar Three: The Data Feedback Loop.&lt;/strong&gt; This is often overlooked, but it's the core of the model. Every time you use the software, you're helping the company train their models. For free. Your prompts, your documents, your patterns—all feeding back into their system.&lt;/p&gt;

&lt;p&gt;So the essence of cloud-based AI isn't selling a service. It's collecting an &lt;strong&gt;intelligence tax&lt;/strong&gt;. As long as you're using their software, you remain a digital tenant in their system.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Local AI Actually Represents
&lt;/h2&gt;

&lt;p&gt;In financial terms, this shift is simple: moving from operating expenses (OpEx) to capital expenditure (CapEx).&lt;/p&gt;

&lt;p&gt;Cloud-based AI is like renting an apartment. You pay every month—that's the subscription fee. And you'll notice it gets more expensive the longer you stay. Price increases. New tiers. "Premium" features that used to be included.&lt;/p&gt;

&lt;p&gt;Local AI is like buying property. You spend $1,000-1,500 once on hardware. After that, your marginal cost drops to nearly zero—just electricity.&lt;/p&gt;

&lt;p&gt;Tools like OpenClaw make this concrete. You download an agent that runs entirely on your machine. It can access your local files, manage your tasks, integrate with your workflow. And unlike cloud AI, it doesn't phone home.&lt;/p&gt;

&lt;p&gt;What big companies fear isn't one Mac mini. They fear &lt;strong&gt;compute power transforming from a service you must continuously rent into a private asset you own outright.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Once users taste the economics of ownership, the valuation logic of SaaS starts to crack.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Big Tech Actually Loses
&lt;/h2&gt;

&lt;p&gt;If this trend continues, what do cloud AI companies really lose?&lt;/p&gt;

&lt;p&gt;Not just subscription fees. &lt;strong&gt;The data flywheel stops spinning.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When AI runs locally—processing your documents, your chats, your private files on your own hardware—the cloud never sees it. The feedback loop breaks. The training data dries up.&lt;/p&gt;

&lt;p&gt;This matters because cloud AI's true moat was never the model itself. Models are becoming commoditized. Open-weights alternatives are closing the gap every month.&lt;/p&gt;

&lt;p&gt;The real moat was whether users stayed locked into their servers. Whether you had to keep feeding the machine to use the machine.&lt;/p&gt;

&lt;p&gt;When that lock gets picked, the moat runs dry.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Honest Trade-Off
&lt;/h2&gt;

&lt;p&gt;I don't want to over-hype local AI. It's not the right choice for everyone today.&lt;/p&gt;

&lt;p&gt;If you need the most cutting-edge reasoning, the largest context windows, the lowest maintenance overhead—cloud AI is still the practical choice. Frontier models like Claude and GPT-4 maintain an edge on complex tasks. And some people genuinely prefer paying someone else to handle the infrastructure.&lt;/p&gt;

&lt;p&gt;But the rise of local agents marks something important: &lt;strong&gt;a return of power.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It proves to the market that, if we choose, we don't have to be permanent tenants. We don't have to be data batteries. The option to own exists—and it's becoming more viable every month.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Question
&lt;/h2&gt;

&lt;p&gt;Here's what I want to leave you with:&lt;/p&gt;

&lt;p&gt;If local AI reaches 80% of cloud AI's capability—good enough for most daily tasks—would you still pay rent every month? Or would you rather buy out your digital assistant once and own it forever?&lt;/p&gt;

&lt;p&gt;The technical gap is closing. The economic math is shifting. The only question is whether you want to keep subscribing, or start owning.&lt;/p&gt;

&lt;p&gt;The choice is yours.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>opensource</category>
      <category>hardware</category>
    </item>
    <item>
      <title>AGENTS.md vs. Skills: How We Refactored OpenClaw to Fix AI Hallucinations</title>
      <dc:creator>Behram</dc:creator>
      <pubDate>Mon, 02 Feb 2026 15:15:46 +0000</pubDate>
      <link>https://dev.to/behruamm/the-death-of-black-box-skills-3ggm</link>
      <guid>https://dev.to/behruamm/the-death-of-black-box-skills-3ggm</guid>
      <description>&lt;p&gt;I bet everyone has had this experience.&lt;/p&gt;

&lt;p&gt;You ask your AI to use the new Gemini 3.0 Pro model, and it argues with you: &lt;em&gt;"That model is invalid, I will use 1.5 Pro instead."&lt;/em&gt;&lt;br&gt;
Or you are working on a Next.js project, and the AI keeps debating you, insisting on using old &lt;code&gt;getStaticProps&lt;/code&gt; syntax when you are clearly using the App Router.&lt;/p&gt;

&lt;p&gt;It is exhausting. You enforce rules, you add docs, you install MCP servers, you build custom "Skills"... and it &lt;strong&gt;still&lt;/strong&gt; hallucinates. You feel like you are just piling rule after rule on top of a broken foundation.&lt;/p&gt;

&lt;p&gt;I was stuck in this loop for weeks. I built complex "Research Skills" designed to force the AI to be smart, but they just turned into black boxes. I pushed a button, the AI disappeared into a script, and it came back with wrong answers (like telling me a £716 visa cost £70k).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Then, last week, I saw an article that solved everything.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://vercel.com/blog/agents-md-outperforms-skills-in-our-agent-evals" rel="noopener noreferrer"&gt;Vercel's AI team published research&lt;/a&gt; that completely flipped my perspective. They found that simply dividing your project knowledge into &lt;strong&gt;Indices&lt;/strong&gt; (in a markdown file) vs. &lt;strong&gt;Skills&lt;/strong&gt; (executable code) changed the game.&lt;/p&gt;

&lt;p&gt;I immediately tried it on my OpenClaw agent. I deleted my complex "Black Box" skills and replaced them with a simple &lt;code&gt;AGENTS.md&lt;/code&gt; index.&lt;/p&gt;

&lt;p&gt;The result? It worked perfectly. The hallucinations stopped. The "syntax debates" ended. Here is why—and how you can do it too.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Vercel Wake-Up Call
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://vercel.com/blog/agents-md-outperforms-skills-in-our-agent-evals" rel="noopener noreferrer"&gt;Vercel's AI SDK team published research&lt;/a&gt; testing this exact problem on coding agents.&lt;/p&gt;

&lt;p&gt;They compared two methods for teaching an AI about Next.js 16:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;strong&gt;Skills (Tools):&lt;/strong&gt; Giving the AI a tool to "Look up documentation."&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;Context (AGENTS.md):&lt;/strong&gt; Just putting the documentation index in a markdown file in the root directory.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The results were brutal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Skills:&lt;/strong&gt; 53% Pass Rate. (The AI often forgot to use the tool, or used it wrong).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Context (&lt;code&gt;AGENTS.md&lt;/code&gt;):&lt;/strong&gt; &lt;strong&gt;100% Pass Rate.&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why? Because &lt;strong&gt;Skills require a decision.&lt;/strong&gt; The AI has to stop and think, &lt;em&gt;"Should I check the docs?"&lt;/em&gt; Often, it gets lazy and guesses.&lt;br&gt;
&lt;strong&gt;Context is passive.&lt;/strong&gt; The instructions are just &lt;em&gt;there&lt;/em&gt;. The AI doesn't have to choose to be smart; it has no choice but to see the map.&lt;/p&gt;
&lt;h2&gt;
  
  
  Refactoring OpenClaw: The "Hands vs. Brains" Split
&lt;/h2&gt;

&lt;p&gt;We took this data and immediately refactored our entire agent stack. We realized we were making a fundamental architecture mistake.&lt;/p&gt;

&lt;p&gt;We were building &lt;strong&gt;Skills&lt;/strong&gt; for things that should have been &lt;strong&gt;Context&lt;/strong&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  The Old Way (Black Box)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;Task:&lt;/strong&gt; "Research this."&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Mechanism:&lt;/strong&gt; &lt;code&gt;Call Tool: Research_Skill()&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;Reality:&lt;/strong&gt; The AI offloads thinking to a hidden script. It stops being an intelligence and becomes a button-pusher.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  The New Way (The Hybrid Stack)
&lt;/h3&gt;

&lt;p&gt;We split our architecture into two distinct layers: &lt;strong&gt;Hands&lt;/strong&gt; and &lt;strong&gt;Brains&lt;/strong&gt;.&lt;/p&gt;
&lt;h4&gt;
  
  
  1. Brains (&lt;code&gt;AGENTS.md&lt;/code&gt; + &lt;code&gt;docs/&lt;/code&gt;)
&lt;/h4&gt;

&lt;p&gt;This is for &lt;strong&gt;Knowledge, Rules, and Logic.&lt;/strong&gt;&lt;br&gt;
We deleted the &lt;code&gt;Research.ts&lt;/code&gt; skill entirely. In its place, we added a simple file: &lt;code&gt;docs/research.md&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Research Protocol&lt;/span&gt;
&lt;span class="p"&gt;1.&lt;/span&gt; &lt;span class="gs"&gt;**Source of Truth:**&lt;/span&gt; Always check official docs (.gov, .org) first.
&lt;span class="p"&gt;2.&lt;/span&gt; &lt;span class="gs"&gt;**Citation:**&lt;/span&gt; You must link every claim.
&lt;span class="p"&gt;3.&lt;/span&gt; &lt;span class="gs"&gt;**Limit:**&lt;/span&gt; Max 5 searches per topic.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In &lt;code&gt;AGENTS.md&lt;/code&gt; (the file the AI always sees), we just added one line:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;For research tasks, READ &lt;code&gt;docs/research.md&lt;/code&gt; first.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  2. Hands (&lt;code&gt;skills/&lt;/code&gt;)
&lt;/h4&gt;

&lt;p&gt;This is for &lt;strong&gt;Execution only.&lt;/strong&gt;&lt;br&gt;
We kept skills for things the AI physically cannot do with its brain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;code&gt;git&lt;/code&gt; (Running terminal commands)&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;whatsapp&lt;/code&gt; (Sending API requests)&lt;/li&gt;
&lt;li&gt;  &lt;code&gt;remindctl&lt;/code&gt; (Talking to macOS)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Result: Transparency
&lt;/h2&gt;

&lt;p&gt;Now, when I ask: &lt;em&gt;"Research the cost of a UK Global Talent Visa."&lt;/em&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; The AI reads &lt;code&gt;AGENTS.md&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; It sees the rule: &lt;em&gt;"Read &lt;code&gt;docs/research.md&lt;/code&gt;."&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt; It reads the protocol: &lt;em&gt;"Check official sources."&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt; &lt;strong&gt;I see it work:&lt;/strong&gt; I see it generating the search query: &lt;code&gt;site:gov.uk global talent visa fee&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; It returns: &lt;em&gt;"The application fee is £716. Note: Some consultants charge £70k, but that is a service fee, not the visa cost."&lt;/em&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;It worked. Not because I wrote better code, but because I stopped trying to code the thinking process.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Guide: When to use what?
&lt;/h2&gt;

&lt;p&gt;If you are building an AI agent (with Cursor, Claude, or OpenClaw), stop building complex tool chains for everything. Use this heuristic:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Requirement&lt;/th&gt;
&lt;th&gt;Use This&lt;/th&gt;
&lt;th&gt;Why?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;"I need you to know X"&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;AGENTS.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Knowledge should be passive. Don't make the AI "search" for your coding style.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;"I need you to follow process Y"&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;docs/Y.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Rules belong in markdown. They are easier to edit and easier for the AI to read.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;"I need you to touch Z"&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Skill&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;If it needs an API key or a CLI command, wrap it in a tool.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Start Small: The "Agile" Agent
&lt;/h2&gt;

&lt;p&gt;Don't over-engineer. Start with a single &lt;code&gt;AGENTS.md&lt;/code&gt; file in your root.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Add your project structure.&lt;/li&gt;
&lt;li&gt;  Add your preferred tech stack.&lt;/li&gt;
&lt;li&gt;  Add a link to your docs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Watch your agent's IQ double overnight. The best tool you can give your AI isn't a Python script; it's a good ReadMe.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>devops</category>
      <category>productivity</category>
      <category>webdev</category>
    </item>
    <item>
      <title>NVIDIA PersonaPlex: The "Full-Duplex" Revolution</title>
      <dc:creator>Behram</dc:creator>
      <pubDate>Mon, 26 Jan 2026 22:31:52 +0000</pubDate>
      <link>https://dev.to/behruamm/nvidia-personaplex-the-full-duplex-revolution-42jp</link>
      <guid>https://dev.to/behruamm/nvidia-personaplex-the-full-duplex-revolution-42jp</guid>
      <description>&lt;p&gt;I have spent the last month building real-time voice agents. I started with the standard stack: LiveKit and Gemini 2.5.&lt;/p&gt;

&lt;p&gt;Even though the latency is impressively low, it still feels far from a natural conversation. Talking to these state-of-the-art models usually feels like playing a turn-based video game.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;My turn:&lt;/strong&gt; I speak.&lt;br&gt;
&lt;strong&gt;System turn:&lt;/strong&gt; It waits for silence. It thinks. It speaks.&lt;/p&gt;

&lt;p&gt;This is "Half-Duplex" logic. It is like using a Walkie-Talkie. The system forces you to wait. But real conversation is "Full-Duplex". We interrupt each other. We laugh at the same time. We hum while listening.&lt;/p&gt;

&lt;p&gt;For the last two days, I have been working with NVIDIA's PersonaPlex (based on Moshi/Mimi). It is completely different. It does not wait for you to stop talking.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Code Proves It
&lt;/h2&gt;

&lt;p&gt;I looked at the backend code to understand why it feels so different. The secret is in &lt;code&gt;moshi/server.py&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In standard agents, you have a loop that waits for an "End of Turn" signal. In PersonaPlex, I found this in the &lt;code&gt;ServerState&lt;/code&gt; initialization:&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="c1"&gt;# moshi/moshi/server.py L115
&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mimi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;streaming_forever&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;other_mimi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;streaming_forever&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lm_gen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;streaming_forever&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is literally "streaming forever." The model processes my voice and its own voice at the same time, 12 times every second. It predicts silence or speech constantly. It does not need "permission" to speak.&lt;/p&gt;

&lt;h2&gt;
  
  
  Realism is Overrated; Rhythm is Everything
&lt;/h2&gt;

&lt;p&gt;Most AI voices feel like "fake meat"—they sound human but act robotic. PersonaPlex is different. It trades audio quality for speed.&lt;/p&gt;

&lt;p&gt;To hit a 240ms reaction time, the audio runs at 24kHz (confirmed in &lt;code&gt;loaders.py&lt;/code&gt; as &lt;code&gt;SAMPLE_RATE = 24000&lt;/code&gt;). I run this command on my voice files to match the training environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ffmpeg &lt;span class="nt"&gt;-i&lt;/span&gt; input &lt;span class="nt"&gt;-ar&lt;/span&gt; 24000 &lt;span class="nt"&gt;-ac&lt;/span&gt; 1 &lt;span class="nt"&gt;-c&lt;/span&gt;:a pcm_s16le &lt;span class="nt"&gt;-af&lt;/span&gt; &lt;span class="s2"&gt;"lowpass=f=8000"&lt;/span&gt; output.wav
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is lo-fi, but the rhythm is perfect. The model relies on consistent "Chatterbox TTS" data and learns from "negative-duration silence" during training. This forces it to understand that conversation involves overlapping, not just waiting. It might sound synthetic, but it laughs and interrupts exactly like a human.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Body &amp;amp; Brain Split
&lt;/h2&gt;

&lt;p&gt;PersonaPlex separates "how it sounds" from "what it thinks."&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The Body (Voice Prompt):&lt;/strong&gt; A 15-second audio clip for acoustics (loaded via &lt;code&gt;lm_gen.load_voice_prompt&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The Brain (Text Prompt):&lt;/strong&gt; Instructions for behavior.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The system pre-loads the voice to save time (reducing latency). But they must match. You cannot use a calm "Customer Service" voice with an "Angry Pirate" text prompt—the model will glitch because the acoustic skeleton fights the semantic brain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Unlocking "Social Mode"
&lt;/h2&gt;

&lt;p&gt;To stop it from acting like a boring assistant, use this specific trigger phrase found in the training data (and verified in the server code's system tagging):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"You enjoy having a good conversation."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Combine this with a high-energy voice sample, and it switches modes. It starts laughing, interrupting, and "vibing" instead of just solving tasks.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Reality Check (Trade-offs)
&lt;/h2&gt;

&lt;p&gt;While the roadmap shows tool-calling is coming next, there are still significant hurdles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Context Limits&lt;/strong&gt;: The model has a fixed context window (defined as &lt;code&gt;context: 3000&lt;/code&gt; frames in &lt;code&gt;loaders.py&lt;/code&gt;). At 12.5Hz, this translates to roughly 240 seconds of memory. My tests show it often gets unstable around 160 seconds.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stability&lt;/strong&gt;: Overlapping speech feels natural until it gets buggy. Sometimes the model will just speak over you non-stop.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost&lt;/strong&gt;: "Infinite streaming" requires high-end NVIDIA GPUs (A100/H100).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Complexity&lt;/strong&gt;: Managing simultaneous audio/text streams is far more complex than standard WebSockets.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Despite these issues, PersonaPlex is the first model I have used that feels like a natural customer service agent rather than a text-to-speech bot.&lt;/p&gt;

&lt;p&gt;Welcome to follow me on &lt;a href="https://behruamm.substack.com/" rel="noopener noreferrer"&gt;Substack&lt;/a&gt; as I will release more deep tests and analyses after spending some time with the model.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>tutorial</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Gemini 2.5 Native Audio + LiveKit: A Production Setup Guide</title>
      <dc:creator>Behram</dc:creator>
      <pubDate>Fri, 16 Jan 2026 21:55:15 +0000</pubDate>
      <link>https://dev.to/behruamm/gemini-25-native-audio-livekit-a-production-setup-guide-2nin</link>
      <guid>https://dev.to/behruamm/gemini-25-native-audio-livekit-a-production-setup-guide-2nin</guid>
      <description>&lt;p&gt;10-minute tutorials make voice AI look simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;roomName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;room&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// Done! ✨&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Production reality is different:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Who creates the room?&lt;/li&gt;
&lt;li&gt;How does the agent identify users?&lt;/li&gt;
&lt;li&gt;What happens when failures occur?&lt;/li&gt;
&lt;li&gt;How do you prevent duplicate agents?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After shipping &lt;strong&gt;1000+ voice AI sessions&lt;/strong&gt; using &lt;strong&gt;LiveKit + Gemini Realtime&lt;/strong&gt; on Next.js/Cloud Run, here's what tutorials skip.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This article covers:&lt;/strong&gt; JWT auth, auto-dispatch patterns, audio subscription timing, greeting guards, and production robustness.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Prerequisites:&lt;/strong&gt; Basic Next.js, LiveKit Cloud account, Firebase Auth configured.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Complete Flow (11 Steps)
&lt;/h2&gt;

&lt;p&gt;Before diving into code, understand the full sequence:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌─────────────┐
│   Browser   │  1. User clicks "Start Interview"
└──────┬──────┘
       │ 2. POST /api/start-bot
       │    Authorization: Bearer &amp;lt;firebase-token&amp;gt;
       ▼
┌─────────────────┐
│  Next.js API    │  3. Verify token with Firebase
│ /api/start-bot  │  4. Create LiveKit room
└────────┬────────┘  5. Generate JWT + metadata
         │           6. Return token to browser
         ▼
┌───────────────────────┐
│   LiveKit Cloud       │  7. Browser connects
│ wss://your.livekit... │  8. Auto-dispatch agent
└───────────────────────┘
         │
         ▼
┌───────────────────────┐
│  Cloud Run Worker     │  9. entrypoint() runs
│  (Voice Agent)        │  10. Load user state from DB
└───────────────────────┘  11. Generate personalized greeting
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This isn't just "connect to a room"—it's an &lt;strong&gt;orchestrated sequence&lt;/strong&gt; where order matters.&lt;/p&gt;

&lt;p&gt;Let's break down each part with the mistakes I made.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 1: The JWT Token Factory (Backend API)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  ❌ Mistake #1: Exposing Credentials to Browser
&lt;/h3&gt;

&lt;p&gt;I've seen this in production codebases:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// NEVER DO THIS&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AccessToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_LIVEKIT_API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// ❌ EXPOSED IN BROWSER&lt;/span&gt;
  &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;NEXT_PUBLIC_LIVEKIT_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// ❌ SECURITY DISASTER&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why it's terrible:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Anyone can create tokens for ANY room&lt;/li&gt;
&lt;li&gt;Attackers can impersonate users&lt;/li&gt;
&lt;li&gt;Zero authentication&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  ✅ The Right Way: Server-Side Token Generation
&lt;/h3&gt;

&lt;p&gt;Create a Next.js API route that verifies the user BEFORE creating tokens:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// frontend/src/app/api/start-bot/route.ts&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;next/server&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RoomServiceClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;AccessToken&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;livekit-server-sdk&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;adminAuth&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/lib/firebase-admin&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;POST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;NextRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// STEP 1: Verify Firebase token (The Gatekeeper)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authHeader&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Authorization&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;authHeader&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nf"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bearer &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Unauthorized&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;idToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;authHeader&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Bearer &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Server-side verification with Firebase Admin SDK&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;decodedToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;adminAuth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;verifyIdToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;idToken&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;decodedToken&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;uid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// ⭐ REAL verified user ID&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;authError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invalid Token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// STEP 2: LiveKit setup&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;livekitUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LIVEKIT_URL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apiKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LIVEKIT_API_KEY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apiSecret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LIVEKIT_API_SECRET&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;roomService&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RoomServiceClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;livekitUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;apiSecret&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// STEP 3: Create room&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;room_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;`room-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;36&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;substring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;roomService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createRoom&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;room_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;emptyTimeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;// Auto-cleanup after 60s&lt;/span&gt;
    &lt;span class="na"&gt;maxParticipants&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;    &lt;span class="c1"&gt;// 1 user + 1 agent only&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// STEP 4: Generate token with verified identity&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AccessToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;apiSecret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;identity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// ⭐ Embedded in token&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="nx"&gt;at&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addGrant&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;roomJoin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;room&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;room_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;at&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toJwt&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;NextResponse&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;room_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;identity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user_id&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key Decisions:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Server generates tokens&lt;/strong&gt; (credentials never touch browser)&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Firebase verifies user&lt;/strong&gt; BEFORE creating room&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;User ID embedded&lt;/strong&gt; as &lt;code&gt;identity&lt;/code&gt; in LiveKit token&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;Random room names&lt;/strong&gt; (users can't guess existing rooms)&lt;/p&gt;


&lt;h2&gt;
  
  
  Part 2: Explicit vs Auto-Dispatch
&lt;/h2&gt;

&lt;p&gt;Now that you have a room and token, how does the agent join?&lt;/p&gt;
&lt;h3&gt;
  
  
  Pattern A: Manual Dispatch (What I Used First)
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Create token&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;at&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toJwt&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Separately dispatch agent&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;roomService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dispatchAgent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;agentName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;my-agent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;room&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;room_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Problems I Hit:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Requires 2 API calls to LiveKit&lt;/li&gt;
&lt;li&gt;Race condition: User joins before agent&lt;/li&gt;
&lt;li&gt;If dispatch fails, user sits in empty room&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Pattern B: Auto-Dispatch via JWT ⭐ (Production Choice)
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RoomConfiguration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;RoomAgentDispatch&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@livekit/protocol&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// Attach dispatch config to user's token&lt;/span&gt;
&lt;span class="nx"&gt;at&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;roomConfig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RoomConfiguration&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;agents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;RoomAgentDispatch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;agentName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;noah-voice-agent&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;at&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toJwt&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Why it's better:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Atomic&lt;/strong&gt;: Agent dispatch happens when user joins&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;LiveKit handles retries&lt;/strong&gt; (more reliable)&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;One API call&lt;/strong&gt; instead of two&lt;br&gt;&lt;br&gt;
✅ &lt;strong&gt;No race conditions&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real Impact:&lt;/strong&gt; Reduced agent dispatch failures from ~5% to &amp;lt;0.1%.&lt;/p&gt;


&lt;h2&gt;
  
  
  Part 3: Agent Entry Point - The Critical Pattern
&lt;/h2&gt;

&lt;p&gt;Your agent receives a job when the user joins. Here's where most tutorials fail you.&lt;/p&gt;
&lt;h3&gt;
  
  
  ❌ Mistake #2: Waiting for Participant Before Starting Session
&lt;/h3&gt;

&lt;p&gt;This looks logical but breaks in production:&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="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;entrypoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;JobContext&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Wait for user to appear
&lt;/span&gt;    &lt;span class="n"&gt;participant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;wait_for_participant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;room&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;participant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;identity&lt;/span&gt;

    &lt;span class="c1"&gt;# Load their data from database
&lt;/span&gt;    &lt;span class="n"&gt;data&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;load_user_state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MyAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# Start session
&lt;/span&gt;    &lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AgentSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;...)&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;room&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;room&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# ❌ TOO LATE!
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Symptoms:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Agent connects but can't hear user&lt;/li&gt;
&lt;li&gt;Logs show: &lt;code&gt;subscribed=False&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Works in local testing (lucky timing)&lt;/li&gt;
&lt;li&gt;Fails in production randomly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Root Cause:&lt;/strong&gt; Audio subscription happens &lt;strong&gt;inside&lt;/strong&gt; &lt;code&gt;session.start()&lt;/code&gt;. If you wait for participant identity first, you miss the subscription window.&lt;/p&gt;

&lt;h3&gt;
  
  
  ✅ The Fix: Start Session First, Then Personalize
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;entrypoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;JobContext&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# STEP 1: Create placeholder agent
&lt;/span&gt;    &lt;span class="n"&gt;placeholder_db&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DatabaseService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pending&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;InterviewAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pending&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;placeholder_db&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# STEP 2: Create session
&lt;/span&gt;    &lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AgentSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;google&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;realtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;RealtimeModel&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-live-2.5-flash-native-audio&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;voice&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Puck&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;instructions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;instructions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;vertexai&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# STEP 3: Start session IMMEDIATELY (SDK subscribes to audio here)
&lt;/span&gt;    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;room&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;room&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;✅ Session started - audio pipeline active&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# STEP 4: NOW get participant (session already listening)
&lt;/span&gt;    &lt;span class="n"&gt;participant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;  &lt;span class="c1"&gt;# 30s timeout
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;room&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remote_participants&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;participant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;room&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remote_participants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;())[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;participant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;⚠️ No participant after 30s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;participant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;identity&lt;/span&gt;  &lt;span class="c1"&gt;# ⭐ This is Firebase UID from token
&lt;/span&gt;    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&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;✅ User identity: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user_id&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="c1"&gt;# STEP 5: Hydrate agent with real data
&lt;/span&gt;    &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;
    &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db_service&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;DatabaseService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;initial_data&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;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;db_service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_candidate_data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&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;❌ DB failed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&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;initial_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;  &lt;span class="c1"&gt;# Fallback to fresh session
&lt;/span&gt;
    &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;initial_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;initial_data&lt;/span&gt;

    &lt;span class="c1"&gt;# STEP 6: Determine phase (new vs resume)
&lt;/span&gt;    &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_phase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_determine_initial_phase&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;initial_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;# STEP 7: Generate personalized greeting
&lt;/span&gt;    &lt;span class="n"&gt;greeting&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_greeting_instruction&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instructions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;greeting&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;✅ Greeting triggered&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The Pattern:&lt;/strong&gt; Start → Listen → Identify → Personalize → Greet&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Impact:&lt;/strong&gt; 100% audio subscription success rate.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 4: The Greeting Guard Pattern
&lt;/h2&gt;

&lt;p&gt;Even with audio working, there's another trap.&lt;/p&gt;

&lt;h3&gt;
  
  
  ❌ Mistake #3: Tools Fire During Greeting
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;What happens:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agent: "Hi, I'm Noah, your AI career c—"
User: "Hello!" (eager)
Gemini: *calls process_response() mid-greeting*
Database: *saves garbage data*
Agent: *confused about conversation state*
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  ✅ Solution: Greeting Guard Flag
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;InterviewAgent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;super&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;instructions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;SYSTEM_PROMPT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;greeting_complete&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;  &lt;span class="c1"&gt;# ⭐ Start locked
&lt;/span&gt;
    &lt;span class="nd"&gt;@function_tool&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;process_user_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="c1"&gt;# GUARD: Block tool execution during greeting
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;greeting_complete&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SYSTEM: Wait for greeting to complete. Do not process yet.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

        &lt;span class="c1"&gt;# Normal logic continues here
&lt;/span&gt;        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;self&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="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="bp"&gt;...&lt;/span&gt;

&lt;span class="c1"&gt;# Unlock tools after greeting completes
&lt;/span&gt;&lt;span class="nd"&gt;@session.on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;agent_state_changed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_state_changed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;old_state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;speaking&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new_state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;listening&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;greeting_complete&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;greeting_complete&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
            &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;✅ Greeting done, tools unlocked&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why it works:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Agent speaks full greeting uninterrupted&lt;/li&gt;
&lt;li&gt;Tools unlock only after state transition: &lt;code&gt;speaking&lt;/code&gt; → &lt;code&gt;listening&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;No premature database writes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Real Data:&lt;/strong&gt; Eliminated 100% of corrupted session starts.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 5: Robustness Patterns
&lt;/h2&gt;

&lt;p&gt;Production code needs fallbacks. Here are patterns from actual failures.&lt;/p&gt;

&lt;h3&gt;
  
  
  Pattern 1: Timeout with Fallback
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Don't wait forever for participant
&lt;/span&gt;&lt;span class="n"&gt;participant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;room&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remote_participants&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;participant&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;room&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remote_participants&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;())[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;participant&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# FALLBACK: Log and gracefully exit
&lt;/span&gt;    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;⚠️ No participant after 30s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;I can&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;t hear anyone. Please refresh and try again.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt;  &lt;span class="c1"&gt;# Exit cleanly
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pattern 2: Database Connection Fallback
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;initial_data&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;get_candidate_data&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&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;❌ DB connection failed: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;e&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="c1"&gt;# FALLBACK: Start fresh session instead of crashing
&lt;/span&gt;    &lt;span class="n"&gt;initial_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_reply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Hi! Let&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s start fresh today. Tell me about your background.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Pattern 3: Duplicate Agent Prevention
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Problem:&lt;/strong&gt; Health check fails → Cloud Run keeps retrying → Multiple agents speak simultaneously&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt;&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="c1"&gt;# Health check server MUST bind to 0.0.0.0
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;start_health_server&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;port&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;PORT&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;httpd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HTTPServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.0.0.0&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;# ⭐ NOT 'localhost'
&lt;/span&gt;        &lt;span class="n"&gt;HealthCheckHandler&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;httpd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;serve_forever&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Don't override Cloud Run's PORT variable
&lt;/span&gt;&lt;span class="nf"&gt;load_dotenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;override&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# ⭐ Critical
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why:&lt;/strong&gt; Cloud Run expects health checks on &lt;code&gt;0.0.0.0&lt;/code&gt;. If you bind to &lt;code&gt;localhost&lt;/code&gt;, it fails and creates zombie agents.&lt;/p&gt;




&lt;h2&gt;
  
  
  Part 6: Testing the Full Flow
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Local Development Setup
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Terminal 1: Cloud SQL Proxy (if using Cloud SQL)&lt;/span&gt;
./cloud-sql-proxy PROJECT:REGION:INSTANCE &lt;span class="nt"&gt;--port&lt;/span&gt; 5432

&lt;span class="c"&gt;# Terminal 2: Voice Agent&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;voice-agent
&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
python src/main.py dev

&lt;span class="c"&gt;# Terminal 3: Frontend&lt;/span&gt;
&lt;span class="nb"&gt;cd &lt;/span&gt;frontend
npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Critical Test Cases
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Happy Path&lt;/strong&gt;: User joins → Agent greets → Conversation flows&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User Refresh&lt;/strong&gt;: User closes tab mid-call → Reconnects → Session resumes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Database Timeout&lt;/strong&gt;: DB slow → Agent uses fallback greeting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No Participant&lt;/strong&gt;: Room created but user never joins → Agent exits gracefully&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Network Drop&lt;/strong&gt;: User loses connection → Reconnects → Conversation continues&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Lessons Learned: The DO/DON'T Checklist
&lt;/h2&gt;

&lt;h3&gt;
  
  
  DO ✅
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Generate tokens &lt;strong&gt;server-side&lt;/strong&gt; (never expose credentials)&lt;/li&gt;
&lt;li&gt;Embed user identity in JWT token&lt;/li&gt;
&lt;li&gt;Use &lt;strong&gt;auto-dispatch&lt;/strong&gt; via &lt;code&gt;roomConfig&lt;/code&gt; (more reliable than manual)&lt;/li&gt;
&lt;li&gt;Start session &lt;strong&gt;FIRST&lt;/strong&gt;, get identity &lt;strong&gt;AFTER&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Guard tools&lt;/strong&gt; during greeting with state flag&lt;/li&gt;
&lt;li&gt;Add &lt;strong&gt;timeouts and fallbacks&lt;/strong&gt; everywhere&lt;/li&gt;
&lt;li&gt;Bind health server to &lt;code&gt;0.0.0.0&lt;/code&gt; for Cloud Run&lt;/li&gt;
&lt;li&gt;Test with &lt;strong&gt;database failures&lt;/strong&gt; and &lt;strong&gt;network drops&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  DON'T ❌
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Put LiveKit secrets in &lt;code&gt;NEXT_PUBLIC_*&lt;/code&gt; variables&lt;/li&gt;
&lt;li&gt;Wait for participant before starting session&lt;/li&gt;
&lt;li&gt;Allow tools to execute during greeting&lt;/li&gt;
&lt;li&gt;Assume database is always available&lt;/li&gt;
&lt;li&gt;Skip health check server (Cloud Run requires it)&lt;/li&gt;
&lt;li&gt;Override Cloud Run's &lt;code&gt;$PORT&lt;/code&gt; environment variable&lt;/li&gt;
&lt;li&gt;Deploy without testing the full flow locally first&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  From Demo to Production
&lt;/h2&gt;

&lt;p&gt;The gap between LiveKit tutorials and production isn't just code—it's &lt;strong&gt;robustness thinking&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tutorials assume happy paths&lt;/strong&gt; (user joins, everything works)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Production has 10 failure modes&lt;/strong&gt; per integration point&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After 3 weeks of debugging these issues in production, I learned:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Audio subscription is timing-sensitive&lt;/strong&gt; (start session first)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auto-dispatch beats manual dispatch&lt;/strong&gt; (atomic operations win)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State guards prevent race conditions&lt;/strong&gt; (greeting flag pattern)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fallbacks save user experience&lt;/strong&gt; (DB down? Start fresh)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Health checks matter on Cloud Run&lt;/strong&gt; (bind to &lt;code&gt;0.0.0.0&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  What's Next?
&lt;/h2&gt;

&lt;p&gt;In the next article, I'll cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tool calling patterns with complex state machines&lt;/li&gt;
&lt;li&gt;Database-backed session resume&lt;/li&gt;
&lt;li&gt;The 80% CPU problem (and how I fixed it)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Building production voice AI?&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check my &lt;a href="https://github.com/Behruamm/gemini-livekit-demo" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; for code examples&lt;/li&gt;
&lt;li&gt;👉 Subscribe to my &lt;a href="https://behruamm.substack.com/" rel="noopener noreferrer"&gt;Substack&lt;/a&gt; for more real-world patterns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What's your biggest voice AI integration pain point? Comment below.&lt;/strong&gt; 👇&lt;/p&gt;




</description>
      <category>livekit</category>
      <category>gemini</category>
      <category>voiceai</category>
      <category>webrtc</category>
    </item>
  </channel>
</rss>
