<?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>Forem: web3nomad.eth</title>
    <description>The latest articles on Forem by web3nomad.eth (@web3nomad).</description>
    <link>https://forem.com/web3nomad</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%2F3658743%2F10e28100-cdfc-4e47-8b3d-382dc140eaee.jpeg</url>
      <title>Forem: web3nomad.eth</title>
      <link>https://forem.com/web3nomad</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/web3nomad"/>
    <language>en</language>
    <item>
      <title>ClawdBot's Hidden Superpower (It's Not Working While You Sleep)</title>
      <dc:creator>web3nomad.eth</dc:creator>
      <pubDate>Sun, 25 Jan 2026 14:59:16 +0000</pubDate>
      <link>https://forem.com/web3nomad/clawdbots-hidden-superpower-its-not-working-while-you-sleep-3cfj</link>
      <guid>https://forem.com/web3nomad/clawdbots-hidden-superpower-its-not-working-while-you-sleep-3cfj</guid>
      <description>&lt;p&gt;I installed ClawdBot 36 hours ago. Like thousands of others who saw it explode across X, I got caught up in the excitement—the promise of a "24/7 AI assistant" that works while you sleep sounded irresistible.&lt;/p&gt;

&lt;p&gt;But after watching my instance run for a day and a half, and reading through hundreds of posts from early adopters, I noticed something fascinating: &lt;strong&gt;the people getting the most value from ClawdBot aren't the ones trying to make it work 24/7. They're the ones who figured out what it's actually good at.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This isn't a takedown. ClawdBot is genuinely innovative—just not in the way the viral tweets suggest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Everyone (Including Me) Got Excited
&lt;/h2&gt;

&lt;p&gt;The narrative was perfect.&lt;/p&gt;

&lt;p&gt;@AlexFinn: "It built a full project management system for me while I slept."&lt;/p&gt;

&lt;p&gt;@bffmike: "It coded all night while I slept, woke up to progress."&lt;/p&gt;

&lt;p&gt;@leopoldfeldman bought 16 Mac Minis to run multiple instances, calling it his "personal AI data center."&lt;/p&gt;

&lt;p&gt;Who wouldn't want that? An AI that works while you rest. No overtime, no complaints, just pure output accumulating overnight. It's the productivity fantasy we've all had at 11 PM staring at an unfinished project.&lt;/p&gt;

&lt;p&gt;The FOMO was real. Within 48 hours, Mac Mini jokes flooded timelines. People scrambled to set it up, terrified of missing "the future."&lt;/p&gt;

&lt;p&gt;I was one of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Actually Found
&lt;/h2&gt;

&lt;p&gt;Here's what happened in my first 36 hours:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hour 0-12&lt;/strong&gt;: Excitement. Set it up to "monitor my projects" overnight.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hour 12-24&lt;/strong&gt;: Woke up to... a lot of activity. Messages, updates, suggestions. Impressive volume.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hour 24-36&lt;/strong&gt;: Spent 2 hours reviewing what it did. Some useful, some needed correction, some I just... didn't need at 3 AM.&lt;/p&gt;

&lt;p&gt;Then I started looking at how other people were actually using it.&lt;/p&gt;

&lt;p&gt;@bangkokbuild's setup caught my attention:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Monitors earthquakes in Tokyo&lt;/li&gt;
&lt;li&gt;Tracks website visitor analytics
&lt;/li&gt;
&lt;li&gt;Checks in if he's been quiet too long&lt;/li&gt;
&lt;li&gt;Logs sleep and health data, alerts when he stays up late&lt;/li&gt;
&lt;li&gt;Morning brief: weather + health stats + calendar&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;@danpeguine's approach:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Scans RSS feeds for key trends&lt;/li&gt;
&lt;li&gt;Removes spam from incoming mail&lt;/li&gt;
&lt;li&gt;Sends notifications about his son's school tests&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;@dreetje:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Checks incoming mail and removes spam&lt;/li&gt;
&lt;li&gt;Keeps track of costs and splits them after trips&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notice something? &lt;strong&gt;They're not asking it to produce constantly. They're asking it to watch constantly.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That's when it clicked.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Shift: From Worker to Watcher
&lt;/h2&gt;

&lt;p&gt;The viral posts emphasize "output while you sleep"—the image of AI cranking out deliverables as you rest. Blog posts written. Code shipped. Projects built.&lt;/p&gt;

&lt;p&gt;But look at what actually works:&lt;/p&gt;

&lt;p&gt;The people who figured out ClawdBot aren't using it as a overnight shift worker. They're using it as a &lt;strong&gt;vigilance system&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;It's the difference between asking someone to write ten reports while you're gone versus asking them to text you if something important happens.&lt;/p&gt;

&lt;p&gt;One generates volume. The other generates signal.&lt;/p&gt;

&lt;p&gt;Think about @bangkokbuild's earthquake monitoring. That's not "work 24/7." That's "wait 24/7, act when it matters." The AI spends 99.9% of its time doing nothing—just watching seismic data feeds. Then, the instant something triggers, it alerts him.&lt;/p&gt;

&lt;p&gt;Compare that to "write me 10 blog posts overnight":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You get 10 drafts&lt;/li&gt;
&lt;li&gt;You spend morning reading and revising all 10&lt;/li&gt;
&lt;li&gt;Maybe 2 are usable&lt;/li&gt;
&lt;li&gt;The rest was just... activity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The earthquake monitor:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Costs almost nothing (just API calls for data checks)&lt;/li&gt;
&lt;li&gt;Requires zero morning review&lt;/li&gt;
&lt;li&gt;Delivers actual value (you know if there was an earthquake)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;The irony: The most valuable 24/7 AI assistant is the one that barely does anything most of the time.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Innovation (That Everyone's Missing)
&lt;/h2&gt;

&lt;p&gt;Here's what ClawdBot actually breakthrough:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Persistent Context&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It remembers yesterday's conversation. You can @mention it today, and it knows what you meant yesterday. No "previously on..." recap needed. This is huge, but it's not about 24/7 operation—it's about zero context-switching cost.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Lives Where You Already Are&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Telegram. WhatsApp. Discord. You don't open a separate app or remember to check a dashboard. It's already in your pocket, in the chat apps you open 50 times a day.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Always Accessible&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is the real "24/7" value. Not that it's always working, but that it's always reachable. You're on a walk, you think of something, you message it. No laptop needed. No "let me write this down for later."&lt;/p&gt;

&lt;p&gt;Notice none of these are about continuous labor. They're all about &lt;strong&gt;availability without friction&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The comparison isn't "human assistant who sleeps 8 hours" vs "AI that works 24 hours."&lt;/p&gt;

&lt;p&gt;The comparison is "I need to open my laptop, find the right window, remember context" vs "I'm already in Telegram, I just @ it."&lt;/p&gt;

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

&lt;p&gt;We're still learning what AI is actually good at. And there's a pattern to how we learn:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 1&lt;/strong&gt;: See the capability. Get excited about maximum throughput. ("It can generate! Make it generate everything!")&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 2&lt;/strong&gt;: Realize the overhead. ("Wait, I'm spending more time reviewing AI output than I would've spent just doing it myself.")&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Phase 3&lt;/strong&gt;: Find the efficient equilibrium. ("Oh, it's best at X, not Y.")&lt;/p&gt;

&lt;p&gt;ClawdBot early adopters are speedrunning from Phase 1 to Phase 3.&lt;/p&gt;

&lt;p&gt;The people who figured it out aren't trying to maximize AI labor. They're trying to maximize their own attention. They're deploying ClawdBot where vigilance is valuable but human attention is expensive.&lt;/p&gt;

&lt;p&gt;Monitoring earthquakes: valuable vigilance, expensive attention.&lt;br&gt;
Scanning RSS feeds for specific keywords: valuable vigilance, expensive attention.&lt;br&gt;
Tracking whether you replied to an important email: valuable vigilance, expensive attention.&lt;/p&gt;

&lt;p&gt;Writing 10 blog posts overnight: expensive AI compute, still expensive human attention (to review).&lt;/p&gt;

&lt;h2&gt;
  
  
  The Cost Reality (Without Judgment)
&lt;/h2&gt;

&lt;p&gt;Federico Viticci ran ClawdBot for one week. His instance consumed 180 million Anthropic API tokens.&lt;/p&gt;

&lt;p&gt;That's roughly $360 if you're paying per token. He's on Claude Max, but most people aren't.&lt;/p&gt;

&lt;p&gt;I'm not saying this to criticize Federico—his use case probably justified it. But it illustrates something important:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When you ask AI to "work overnight," you're asking it to think continuously.&lt;/strong&gt; And continuous thinking costs continuous tokens.&lt;/p&gt;

&lt;p&gt;Compare that to the monitoring use cases:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check RSS feed every 10 minutes: minimal tokens&lt;/li&gt;
&lt;li&gt;Wait for earthquake data trigger: minimal tokens
&lt;/li&gt;
&lt;li&gt;Scan incoming email for spam: minimal tokens&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The most efficient ClawdBot users stumbled into this naturally. Not because they were optimizing for cost, but because they were optimizing for signal.&lt;/p&gt;

&lt;p&gt;Turns out those two optimizations align perfectly.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This Tells Us About AI Adoption
&lt;/h2&gt;

&lt;p&gt;Every transformative tool goes through this curve. The printing press. Electricity. The internet.&lt;/p&gt;

&lt;p&gt;First reaction: Do everything we did before, but with the new tool.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Printing press → print books that look like hand-copied manuscripts&lt;/li&gt;
&lt;li&gt;Early websites → digital versions of print brochures
&lt;/li&gt;
&lt;li&gt;Early AI → ChatGPT as a faster Google&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Second reaction: Do more of everything.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;More books! More web pages! More AI output!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Third reaction: Wait, what's this actually good at?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Books become different (novels, newspapers, pamphlets)&lt;/li&gt;
&lt;li&gt;Websites become interactive, not just informative&lt;/li&gt;
&lt;li&gt;AI becomes... we're figuring this out right now&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;ClawdBot's viral moment is us cycling through reactions one and two. The people getting real value have landed on three.&lt;/p&gt;

&lt;p&gt;And reaction three looks like: &lt;strong&gt;AI as an always-on sensor network, not an always-on labor force.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Future Is On-Call, Not Always-On
&lt;/h2&gt;

&lt;p&gt;The best AI tools won't be the ones that work 24/7.&lt;/p&gt;

&lt;p&gt;They'll be the ones that know when to interrupt you and when to stay quiet.&lt;/p&gt;

&lt;p&gt;When to wake you up for an earthquake and when to let you sleep through another crypto price fluctuation.&lt;/p&gt;

&lt;p&gt;When to flag an email that needs your attention now and when to just archive the newsletter you never read.&lt;/p&gt;

&lt;p&gt;When to suggest a better approach to your code and when to let you work through the problem yourself.&lt;/p&gt;

&lt;p&gt;ClawdBot gets us closer to that future—not because it never sleeps, but because it's always reachable when we need it.&lt;/p&gt;

&lt;p&gt;The people who figured this out aren't using it as a replacement for their own thinking. They're using it as an extension of their attention span—a way to keep an eye on things that matter without actually keeping an eye on them.&lt;/p&gt;

&lt;p&gt;That's the real innovation.&lt;/p&gt;

&lt;p&gt;Not that it works while you sleep. But that it watches while you live.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;The pattern I'm seeing:&lt;/strong&gt; The tools we'll still be talking about in 2027 won't be the ones that "do more." They'll be the ones that help us do less—but better.&lt;/p&gt;

&lt;p&gt;ClawdBot is one of them. Just not for the reasons going viral.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How We Migrated atypica.AI to AI SDK v5 Without Breaking 10M+ Chat Histories</title>
      <dc:creator>web3nomad.eth</dc:creator>
      <pubDate>Sun, 18 Jan 2026 04:29:18 +0000</pubDate>
      <link>https://forem.com/web3nomad/how-we-migrated-atypicaai-to-ai-sdk-v5-without-breaking-10m-chat-histories-11lk</link>
      <guid>https://forem.com/web3nomad/how-we-migrated-atypicaai-to-ai-sdk-v5-without-breaking-10m-chat-histories-11lk</guid>
      <description>&lt;p&gt;atypica.AI is a multi-agent research platform where AI agents collaborate to conduct user research. Users ask business questions, and the system orchestrates multiple specialized agents—Study Agent plans research, Scout Agent discovers target users from social media, Interview Agent conducts automated interviews with AI personas, and Report Agent generates comprehensive insights. The entire process involves 30+ AI tools and generates extensive chat histories stored in PostgreSQL.&lt;/p&gt;

&lt;p&gt;When Vercel released AI SDK v5 with breaking changes to message formats and tool APIs, we faced a challenge: migrate 200 files and 30+ tools while keeping 10 million+ existing chat conversations accessible. This article documents our 3-day migration journey.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Final stats&lt;/strong&gt;: 200 files changed, 4,206 insertions, 3,094 deletions, 27 commits.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Core Breaking Change: Message → UIMessage
&lt;/h2&gt;

&lt;p&gt;The most fundamental change in v5 is how messages are structured.&lt;/p&gt;

&lt;h3&gt;
  
  
  v4: Message with content + parts
&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;// v4 Message&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;msg1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;assistant&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The weather is sunny&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// String representation&lt;/span&gt;
  &lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;                           &lt;span class="c1"&gt;// Structured data (optional)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The weather is &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;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tool-invocation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;toolInvocation&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="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sunny&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In v4, you could use either &lt;code&gt;content&lt;/code&gt; (simple string) or &lt;code&gt;parts&lt;/code&gt; (structured array). Most of our code used &lt;code&gt;content&lt;/code&gt; because it was simpler.&lt;/p&gt;

&lt;h3&gt;
  
  
  v5: UIMessage with parts only
&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;// v5 UIMessage&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;msg1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;assistant&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;                           &lt;span class="c1"&gt;// Only source of truth&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The weather is &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;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tool-getWeather&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;toolCallId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;output-available&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;output&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="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sunny&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="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;The weather is sunny&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;   &lt;span class="c1"&gt;// Auto-generated, read-only&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In v5, &lt;code&gt;parts&lt;/code&gt; is the only source of truth. The &lt;code&gt;content&lt;/code&gt; field is automatically derived from &lt;code&gt;parts&lt;/code&gt; and is read-only.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Migration impact&lt;/strong&gt;: Every place that accessed &lt;code&gt;message.content&lt;/code&gt; needed to be refactored to iterate through &lt;code&gt;message.parts&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Day 1: Understanding the Scope
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Running the Codemod
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install &lt;/span&gt;ai@^5.0.59 zod@^4.0.0
npx @ai-sdk/codemod@latest upgrade
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The codemod handled mechanical replacements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Type name: &lt;code&gt;Message&lt;/code&gt; → &lt;code&gt;UIMessage&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Tool API: &lt;code&gt;parameters&lt;/code&gt; → &lt;code&gt;inputSchema&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Added &lt;code&gt;outputSchema&lt;/code&gt; placeholders&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But it left many FIXME comments because it couldn't understand semantic changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  The First Major Problem: Content Access
&lt;/h3&gt;

&lt;p&gt;We had code like this everywhere:&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;// v4 pattern - accessing content directly&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;lastMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&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="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;lastMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;weather&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="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// For title generation&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&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;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// For logging&lt;/span&gt;
&lt;span class="nx"&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="na"&gt;userMessage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&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;v5 migration&lt;/strong&gt;: We created a utility to extract text from parts:&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;// messageUtils.ts&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getTextFromParts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UIMessage&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;parts&lt;/span&gt;&lt;span class="dl"&gt;"&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;return&lt;/span&gt; &lt;span class="nx"&gt;parts&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&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="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&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="c1"&gt;// Usage&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getTextFromParts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parts&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="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;weather&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="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pattern appeared in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Message persistence (16 files)&lt;/li&gt;
&lt;li&gt;UI components (25 files)&lt;/li&gt;
&lt;li&gt;API routes (15 files)&lt;/li&gt;
&lt;li&gt;Logging and analytics (8 files)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Tool Invocation Structure Changed
&lt;/h3&gt;

&lt;p&gt;This was the second major breaking change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;v4 tool part structure:&lt;/strong&gt;&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="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tool-invocation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;toolInvocation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nl"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;result&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;toolCallId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;call_123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;toolName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;searchPersonas&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;coffee lovers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;personas&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;v5 tool part structure:&lt;/strong&gt;&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="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tool-searchPersonas&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;      &lt;span class="c1"&gt;// Typed as tool-${toolName}&lt;/span&gt;
  &lt;span class="nx"&gt;toolCallId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;call_123&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;output-available&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;        &lt;span class="c1"&gt;// States renamed&lt;/span&gt;
  &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;coffee lovers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="c1"&gt;// args → input&lt;/span&gt;
  &lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;personas&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;// result → output&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 changes&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Flattened structure (no nested &lt;code&gt;toolInvocation&lt;/code&gt; object)&lt;/li&gt;
&lt;li&gt;Type is specific: &lt;code&gt;"tool-${toolName}"&lt;/code&gt; not generic &lt;code&gt;"tool-invocation"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;State names: &lt;code&gt;"call"&lt;/code&gt; → &lt;code&gt;"input-available"&lt;/code&gt;, &lt;code&gt;"result"&lt;/code&gt; → &lt;code&gt;"output-available"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Field names: &lt;code&gt;args&lt;/code&gt; → &lt;code&gt;input&lt;/code&gt;, &lt;code&gt;result&lt;/code&gt; → &lt;code&gt;output&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Added error state: &lt;code&gt;"output-error"&lt;/code&gt; with &lt;code&gt;errorText&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Reasoning Format Changed
&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;// v4&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reasoning&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reasoning&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Let me think...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// v5&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reasoning&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Let me think...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Field name changed from &lt;code&gt;reasoning&lt;/code&gt; to &lt;code&gt;text&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Day 2: Backward Compatibility for 10M+ Messages
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Database Challenge
&lt;/h3&gt;

&lt;p&gt;Our PostgreSQL database stores messages in v4 format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;parts&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;chat_messages&lt;/span&gt; &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'msg_old'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- Returns v4 format with tool-invocation and reasoning fields&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Users expect to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open old chat conversations&lt;/li&gt;
&lt;li&gt;Continue conversations seamlessly&lt;/li&gt;
&lt;li&gt;See tool results from old interactions&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We couldn't migrate the database because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;10M+ messages across 50+ tables&lt;/li&gt;
&lt;li&gt;Downtime was unacceptable&lt;/li&gt;
&lt;li&gt;Risk of data corruption too high&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Solution: Conversion Layer
&lt;/h3&gt;

&lt;p&gt;Created &lt;code&gt;src/ai/v4.ts&lt;/code&gt;:&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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;V4ToolInvocation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;call&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;toolCallId&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="nl"&gt;toolName&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="nl"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;result&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;toolCallId&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="nl"&gt;toolName&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="nl"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;any&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;V4MessagePart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;text&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="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reasoning&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;reasoning&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="c1"&gt;// v4 format&lt;/span&gt;
  &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tool-invocation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;toolInvocation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;V4ToolInvocation&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;V5MessagePart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;UIMessage&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;parts&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;convertToV5MessagePart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;V4MessagePart&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;V5MessagePart&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;V5MessagePart&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Text parts are identical in v4 and v5&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&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;part&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Reasoning: reasoning field → text field&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reasoning&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;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reasoning&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;part&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="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reasoning&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reasoning&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;part&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Already v5&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Tool invocation: nested → flat, args/result → input/output&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tool-invocation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;toolInvocation&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;part&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;inv&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toolInvocation&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="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`tool-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;inv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toolName&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="na"&gt;toolCallId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;inv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toolCallId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;inv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;result&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;output-available&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;input-available&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;inv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;inv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;result&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;inv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;V5MessagePart&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Already v5 format&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;part&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;V5MessagePart&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;h3&gt;
  
  
  Apply Conversion on Read
&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;// messageUtils.ts&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;convertDBMessagesToAIMessages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userChatId&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="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;UIMessage&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;&amp;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;dbMessages&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;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chatMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;userChatId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;orderBy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;asc&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;dbMessages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messageId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;role&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;assistant&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;V4MessagePart&lt;/span&gt;&lt;span class="p"&gt;[]).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;convertToV5MessagePart&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Result&lt;/strong&gt;: Old messages load transparently. New messages save in v5 format. No database migration needed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Type System Reorganization
&lt;/h3&gt;

&lt;p&gt;We have 5 independent chat systems with different tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Study&lt;/strong&gt;: 20+ tools (reasoning, search, interviews, reports, social media)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interview&lt;/strong&gt;: 2 tools (endInterview, requestInteractionForm)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persona&lt;/strong&gt;: 1 tool (endInterview)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NewStudy&lt;/strong&gt;: 1 tool (endInterview)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agents&lt;/strong&gt;: 3 tools (thanks, hello, scout)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Initial approach: One global &lt;code&gt;UIToolConfigs&lt;/code&gt; type.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Problem&lt;/strong&gt;: Type pollution. Study components got autocomplete for Interview tools. Runtime errors from tool type mismatches.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution&lt;/strong&gt;: Domain-specific tool types.&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;// src/ai/tools/types.ts - Study system&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;StudyUITools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;reasoningThinking&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;reasoningThinkingInputSchema&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;reasoningThinkingOutputSchema&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nl"&gt;searchPersonas&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;searchPersonasInputSchema&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;searchPersonasOutputSchema&lt;/span&gt;&lt;span class="o"&gt;&amp;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;// ... 20+ other tools&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;TStudyMessageWithTool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;UIMessage&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;UIDataTypes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;StudyUITools&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// src/app/(interviewProject)/tools/types.ts - Interview system&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;TInterviewUITools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;endInterview&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;endInterviewInputSchema&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;endInterviewOutputSchema&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="nl"&gt;requestInteractionForm&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;input&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;requestInteractionFormInputSchema&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nl"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;infer&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;requestInteractionFormOutputSchema&lt;/span&gt;&lt;span class="o"&gt;&amp;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="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;TInterviewMessageWithTool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;UIMessage&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;UIDataTypes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;TInterviewUITools&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each domain gets its own directory structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;src/app/(myDomain)/
├── tools/
│   ├── types.ts    # Tool type definitions
│   └── ui.tsx      # Tool UI rendering
└── types.ts        # Message type for this domain
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Benefit&lt;/strong&gt;: Full type safety within each domain. No cross-contamination.&lt;/p&gt;

&lt;h2&gt;
  
  
  Day 3: UI Components and Model Messages
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Component Migration Pattern
&lt;/h3&gt;

&lt;p&gt;Before v5, components accessed &lt;code&gt;content&lt;/code&gt; directly:&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;// v4 component&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ChatMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Message&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After v5, components iterate over &lt;code&gt;parts&lt;/code&gt;:&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;// v5 component&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;ChatMessage&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;}:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UIMessage&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Markdown&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/Markdown&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;reasoning&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ReasoningBlock&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/ReasoningBlock&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;        &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&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="s2"&gt;tool-&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="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;ToolDisplay&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="nx"&gt;toolPart&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="sr"&gt;/&amp;gt;&lt;/span&gt;&lt;span class="err"&gt;;
&lt;/span&gt;        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;})}&lt;/span&gt;
    &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&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;Tool detection pattern&lt;/strong&gt;:&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;// v4&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tool-invocation&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;toolName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toolInvocation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toolName&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toolInvocation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// v5&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&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="s2"&gt;tool-&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;toolCallId&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;part&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;toolName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;tool-&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&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;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;output-available&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&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;We migrated 25+ UI components with this pattern.&lt;/p&gt;

&lt;h3&gt;
  
  
  The UIMessage vs ModelMessage Distinction
&lt;/h3&gt;

&lt;p&gt;There's one more important detail: messages &lt;strong&gt;to&lt;/strong&gt; the model have a different format.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;UIMessage&lt;/strong&gt; - From model, for UI:&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="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;UserModelMessage&lt;/strong&gt; - To model:&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="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Hello&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt;  &lt;span class="c1"&gt;// Note: content, not parts&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This only matters when constructing messages for &lt;code&gt;streamText&lt;/code&gt; or &lt;code&gt;generateObject&lt;/code&gt;:&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;UserModelMessage&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="s2"&gt;ai&lt;/span&gt;&lt;span class="dl"&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;result&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;streamText&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;gpt-4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;messages&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="na"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;prompt&lt;/span&gt; &lt;span class="p"&gt;}]&lt;/span&gt;  &lt;span class="c1"&gt;// content for model&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;UserModelMessage&lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;
  &lt;span class="na"&gt;tools&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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We had to fix this in 30+ tool definitions and artifact generators.&lt;/p&gt;

&lt;h3&gt;
  
  
  useChat Hook Changes
&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;// v4&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;append&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;reload&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;initialMessages&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useChat&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;chatId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nf"&gt;experimental_prepareRequestBody&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;requestBody&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="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;requestBody&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;// v5&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;sendMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;regenerate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useChat&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;transport&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;DefaultChatTransport&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;api&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/api/chat/study&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nf"&gt;prepareSendMessagesRequest&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;messages&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="na"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="na"&gt;message&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
          &lt;span class="na"&gt;userChatToken&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="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="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hook method renames:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;append&lt;/code&gt; → &lt;code&gt;sendMessage&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;reload&lt;/code&gt; → &lt;code&gt;regenerate&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;initialMessages&lt;/code&gt; → &lt;code&gt;messages&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Transport config:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;experimental_prepareRequestBody&lt;/code&gt; → &lt;code&gt;prepareSendMessagesRequest&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  File Attachments
&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;// v4&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;experimental_attachments&lt;/span&gt;&lt;span class="p"&gt;:&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="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;file.pdf&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;contentType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/pdf&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;...&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="c1"&gt;// v5 - part of parts array&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;file&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;file.pdf&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;mediaType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;application/pdf&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;...&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Field renames:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;name&lt;/code&gt; → &lt;code&gt;filename&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;contentType&lt;/code&gt; → &lt;code&gt;mediaType&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;experimental_attachments&lt;/code&gt; → part of &lt;code&gt;parts&lt;/code&gt; array&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Streaming Message Persistence
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Key insight&lt;/strong&gt;: In v5, only check &lt;code&gt;parts.length&lt;/code&gt;, not &lt;code&gt;content&lt;/code&gt;.&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;// v4 - check both&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;streamingMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;streamingMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;persistentAIMessageToDB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userChatId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;streamingMessage&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// v5 - only check parts&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;streamingMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;persistentAIMessageToDB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userChatId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;streamingMessage&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;The &lt;code&gt;content&lt;/code&gt; field in v5 is derived from &lt;code&gt;parts&lt;/code&gt;, so checking it is redundant.&lt;/p&gt;

&lt;h2&gt;
  
  
  Critical Gotchas
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Message Content is Read-Only in v5
&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;// v4 - this worked&lt;/span&gt;
&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;New text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// v5 - this does nothing (content is derived)&lt;/span&gt;
&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;New text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// ❌ Silently ignored&lt;/span&gt;

&lt;span class="c1"&gt;// v5 - correct way&lt;/span&gt;
&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;New text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}];&lt;/span&gt; &lt;span class="c1"&gt;// ✅&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Empty Parts vs Empty Content
&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;// v4&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&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="c1"&gt;// v5 - this can be misleading&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&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="c1"&gt;// content is auto-generated, might be empty string&lt;/span&gt;

&lt;span class="c1"&gt;// v5 - correct check&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;text&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Tool State Names Changed
&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;// v4 states&lt;/span&gt;
&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;partial-call&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;call&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;result&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;// v5 states&lt;/span&gt;
&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;input-available&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;output-available&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;output-error&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Don't forget to update all state checks.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Optional Tool Call Names
&lt;/h3&gt;

&lt;p&gt;In edge cases (aborted streams, errors), &lt;code&gt;toolCall.toolName&lt;/code&gt; might be undefined:&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;// Safe access&lt;/span&gt;
&lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toolCalls&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;toolName&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;unknown&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Stream Event Field Names
&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;// v4&lt;/span&gt;
&lt;span class="nx"&gt;onFinish&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;reasoning&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;usage&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;

&lt;span class="c1"&gt;// v5&lt;/span&gt;
&lt;span class="nl"&gt;onFinish&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;reasoningText&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;usage&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  6. Message ID Handling
&lt;/h3&gt;

&lt;p&gt;Client messages might not have IDs:&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="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;persistentAIMessageToDB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userChatId&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="nx"&gt;newMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;newMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nf"&gt;generateId&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;h2&gt;
  
  
  Migration Checklist
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Phase 1: Dependencies&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Update &lt;code&gt;ai&lt;/code&gt; to v5, &lt;code&gt;zod&lt;/code&gt; to v4&lt;/li&gt;
&lt;li&gt;[ ] Run codemod: &lt;code&gt;npx @ai-sdk/codemod@latest upgrade&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] Fix TypeScript errors from &lt;code&gt;Message&lt;/code&gt; → &lt;code&gt;UIMessage&lt;/code&gt; rename&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Phase 2: Content Access&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Find all &lt;code&gt;message.content&lt;/code&gt; access&lt;/li&gt;
&lt;li&gt;[ ] Replace with parts iteration or utility function&lt;/li&gt;
&lt;li&gt;[ ] Update title generation, logging, analytics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Phase 3: Tool Parts&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Update tool detection: &lt;code&gt;"tool-invocation"&lt;/code&gt; → &lt;code&gt;part.type.startsWith("tool-")&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] Change field access: &lt;code&gt;args&lt;/code&gt;/&lt;code&gt;result&lt;/code&gt; → &lt;code&gt;input&lt;/code&gt;/&lt;code&gt;output&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] Update state checks: &lt;code&gt;"call"&lt;/code&gt;/&lt;code&gt;"result"&lt;/code&gt; → &lt;code&gt;"input-available"&lt;/code&gt;/&lt;code&gt;"output-available"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] Add &lt;code&gt;"output-error"&lt;/code&gt; handling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Phase 4: Backward Compatibility&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Create v4 → v5 conversion utility&lt;/li&gt;
&lt;li&gt;[ ] Apply conversion when loading from database&lt;/li&gt;
&lt;li&gt;[ ] Test old chat histories thoroughly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Phase 5: UI Components&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Migrate all components to iterate over &lt;code&gt;parts&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] Update tool rendering logic&lt;/li&gt;
&lt;li&gt;[ ] Update file attachment components&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Phase 6: Tool Definitions&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Change &lt;code&gt;parameters&lt;/code&gt; → &lt;code&gt;inputSchema&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] Add &lt;code&gt;outputSchema&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] Import &lt;code&gt;UserModelMessage&lt;/code&gt; for model messages&lt;/li&gt;
&lt;li&gt;[ ] Use &lt;code&gt;content&lt;/code&gt; (not &lt;code&gt;parts&lt;/code&gt;) when calling &lt;code&gt;streamText&lt;/code&gt;/&lt;code&gt;generateObject&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Phase 7: React Hooks&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Update &lt;code&gt;useChat&lt;/code&gt;: &lt;code&gt;append&lt;/code&gt; → &lt;code&gt;sendMessage&lt;/code&gt;, &lt;code&gt;reload&lt;/code&gt; → &lt;code&gt;regenerate&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] Migrate &lt;code&gt;experimental_prepareRequestBody&lt;/code&gt; → &lt;code&gt;prepareSendMessagesRequest&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Phase 8: Edge Cases&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] Handle missing message IDs&lt;/li&gt;
&lt;li&gt;[ ] Handle undefined &lt;code&gt;toolCall.toolName&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] Update stream event handlers&lt;/li&gt;
&lt;li&gt;[ ] Remove &lt;code&gt;content.trim()&lt;/code&gt; checks&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key Insights
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Content is Dead, Long Live Parts
&lt;/h3&gt;

&lt;p&gt;The shift from &lt;code&gt;content&lt;/code&gt; as source of truth to &lt;code&gt;parts&lt;/code&gt; as source of truth is fundamental. Every message access pattern needs review.&lt;/p&gt;

&lt;p&gt;Budget time for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Finding all &lt;code&gt;message.content&lt;/code&gt; references (use global search)&lt;/li&gt;
&lt;li&gt;Understanding each usage context&lt;/li&gt;
&lt;li&gt;Deciding between iteration or utility function&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. The Codemod is 30% of the Work
&lt;/h3&gt;

&lt;p&gt;The codemod handles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Type renames&lt;/li&gt;
&lt;li&gt;Basic API changes&lt;/li&gt;
&lt;li&gt;Obvious field renames&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It doesn't handle:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Semantic changes (&lt;code&gt;content&lt;/code&gt; access patterns)&lt;/li&gt;
&lt;li&gt;Complex refactors (tool detection logic)&lt;/li&gt;
&lt;li&gt;Domain-specific decisions (type organization)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Budget 70% of time for post-codemod fixes.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Backward Compatibility is Non-Negotiable
&lt;/h3&gt;

&lt;p&gt;Users don't care about your migration. Their old chats must just work.&lt;/p&gt;

&lt;p&gt;Strategies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Convert on read (what we did)&lt;/li&gt;
&lt;li&gt;Convert on write (gradual migration)&lt;/li&gt;
&lt;li&gt;Dual schema (complex but zero risk)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We chose convert-on-read because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Zero downtime&lt;/li&gt;
&lt;li&gt;Simple to implement&lt;/li&gt;
&lt;li&gt;Low risk&lt;/li&gt;
&lt;li&gt;Works transparently&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Type System Organization Matters
&lt;/h3&gt;

&lt;p&gt;With multiple chat systems, proper type organization prevents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tool type pollution&lt;/li&gt;
&lt;li&gt;Runtime errors from wrong tool usage&lt;/li&gt;
&lt;li&gt;Confusing autocomplete&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Invest time in domain-specific types early.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Test Incrementally
&lt;/h3&gt;

&lt;p&gt;We migrated systems in order of complexity:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Agents (simplest, 3 tools) - validate pattern&lt;/li&gt;
&lt;li&gt;NewStudy (1 tool) - test in production&lt;/li&gt;
&lt;li&gt;Persona (1 tool) - confidence building&lt;/li&gt;
&lt;li&gt;Interview (2 tools) - more complex&lt;/li&gt;
&lt;li&gt;Study (20+ tools) - final boss&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each system validated the patterns before applying them broadly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Before v5:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inconsistent access patterns (&lt;code&gt;content&lt;/code&gt; vs &lt;code&gt;parts&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Loose typing around tools&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Message&lt;/code&gt; type used everywhere&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;After v5:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Single source of truth: &lt;code&gt;parts&lt;/code&gt; array&lt;/li&gt;
&lt;li&gt;Full type safety across 30+ tools&lt;/li&gt;
&lt;li&gt;Domain-specific type organization&lt;/li&gt;
&lt;li&gt;Cleaner separation of concerns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Time breakdown:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Day 1: Dependencies, codemod, content access patterns (8 hours)&lt;/li&gt;
&lt;li&gt;Day 2: Backward compatibility, type system (10 hours)&lt;/li&gt;
&lt;li&gt;Day 3: UI components, tool definitions, edge cases (6 hours)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Total: ~24 hours focused work over 3 days.&lt;/p&gt;

&lt;h2&gt;
  
  
  Recommendations
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Budget 3-5 days&lt;/strong&gt;, not 1-2 days&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Start simple&lt;/strong&gt;: Migrate your simplest system first&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build backward compatibility early&lt;/strong&gt;, not as afterthought&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create utilities&lt;/strong&gt;: &lt;code&gt;getTextFromParts()&lt;/code&gt;, &lt;code&gt;convertV4ToV5()&lt;/code&gt;, etc.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Organize types by domain&lt;/strong&gt; if you have multiple systems&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test old data&lt;/strong&gt;: Load historical chats, verify tool results display&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Document the change&lt;/strong&gt;: Team needs to understand &lt;code&gt;parts&lt;/code&gt; is source of truth&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The migration is substantial but manageable with proper planning. v5's parts-based approach is more flexible and type-safe. The pain of migration is short-term; the benefits are long-term.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>softwareengineering</category>
      <category>typescript</category>
    </item>
    <item>
      <title>Universal Agent + Skills Tutorial: Build Your 10-Person Team from Scratch</title>
      <dc:creator>web3nomad.eth</dc:creator>
      <pubDate>Sat, 17 Jan 2026 05:52:20 +0000</pubDate>
      <link>https://forem.com/web3nomad/build-your-10-person-engineering-team-with-universal-agent-1anc</link>
      <guid>https://forem.com/web3nomad/build-your-10-person-engineering-team-with-universal-agent-1anc</guid>
      <description>&lt;h1&gt;
  
  
  Universal Agent + Skills Tutorial: Build Your 10-Person Team from Scratch
&lt;/h1&gt;

&lt;p&gt;Imagine this:&lt;/p&gt;

&lt;p&gt;You tell your AI Agent: "Write me technical documentation." It instantly loads &lt;code&gt;technical-writer.skill&lt;/code&gt; and transforms into a tech writer.&lt;/p&gt;

&lt;p&gt;Next moment: "Analyze our competitors' ad strategies." It swaps in &lt;code&gt;marketing-analyst.skill&lt;/code&gt; and starts crawling data.&lt;/p&gt;

&lt;p&gt;Even better: Need an expert in a niche domain—say, "SQL performance tuning"—but can't find one? &lt;strong&gt;Let the Agent write a skill for itself&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This isn't sci-fi. And it's &lt;strong&gt;ridiculously simple&lt;/strong&gt; to build.&lt;/p&gt;




&lt;p&gt;This guide shows you how to build a &lt;strong&gt;Universal Agent&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ &lt;strong&gt;Load any skill&lt;/strong&gt; - Drop in a &lt;code&gt;.skill&lt;/code&gt; file, Agent instantly gains new capabilities&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Self-authoring skills&lt;/strong&gt; - Agent can create specialized skill packages for itself&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Persistent work&lt;/strong&gt; - Files survive across conversations—code from morning, iterate in afternoon&lt;/li&gt;
&lt;li&gt;✅ &lt;strong&gt;Zero infrastructure&lt;/strong&gt; - No Docker, no K8s. Just a Next.js project.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Think of it as assembling a 10-person founding team: each skill is a specialist, the Agent is the PM, you're the CEO.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User uploads skill packages → S3 storage
                                ↓
Request starts → Load skills + workspace → In-memory sandbox
                                ↓
Agent reads skills, creates files, uses tools
                                ↓
Request ends → Save workspace changes → Disk
                                ↓
Next conversation → Files persisted → Agent continues work
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;bash-tool&lt;/code&gt; - In-memory filesystem with bash, readFile, writeFile&lt;/li&gt;
&lt;li&gt;Skills in &lt;code&gt;sandbox/user/{id}/skills/&lt;/code&gt; (read-only, from S3)&lt;/li&gt;
&lt;li&gt;Workspace in &lt;code&gt;sandbox/user/{id}/workspace/&lt;/code&gt; (read-write, persisted)&lt;/li&gt;
&lt;li&gt;AI SDK handles streaming, tool calls, token tracking&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Core Implementation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Skill Structure
&lt;/h3&gt;

&lt;p&gt;Each skill is a &lt;code&gt;.skill&lt;/code&gt; file (zip format) with a simple structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my-skill/
├── SKILL.md          # Instructions and expertise
└── references/       # Optional: supporting docs
    └── examples.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Example SKILL.md:&lt;/strong&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="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;technical-writer&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Creates technical documentation, API guides, and tutorials.&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

&lt;span class="gh"&gt;# Technical Writer Skill&lt;/span&gt;

You are an expert technical writer who creates clear, concise documentation.

&lt;span class="gu"&gt;## Expertise&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; API documentation with examples
&lt;span class="p"&gt;-&lt;/span&gt; Architecture decision records (ADRs)
&lt;span class="p"&gt;-&lt;/span&gt; User guides and tutorials
&lt;span class="p"&gt;-&lt;/span&gt; Code comments and inline docs

&lt;span class="gu"&gt;## Guidelines&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Start with why, then what, then how
&lt;span class="p"&gt;-&lt;/span&gt; Include practical examples
&lt;span class="p"&gt;-&lt;/span&gt; Use active voice
&lt;span class="p"&gt;-&lt;/span&gt; Keep it DRY—link instead of repeating

&lt;span class="gu"&gt;## Activation&lt;/span&gt;
Activate when user asks to:
&lt;span class="p"&gt;-&lt;/span&gt; "Write documentation for..."
&lt;span class="p"&gt;-&lt;/span&gt; "Create an API guide..."
&lt;span class="p"&gt;-&lt;/span&gt; "Document this codebase..."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Upload flow: User uploads zip → Store in S3 → Reference in database → Agent loads on demand.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Core Route Handler
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;File: &lt;code&gt;app/api/chat/universal/route.ts&lt;/code&gt;&lt;/strong&gt;&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="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;streamText&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="s2"&gt;ai&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;createBashTool&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="s2"&gt;bash-tool&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;loadAllSkillsToMemory&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="s2"&gt;@/lib/skill/loadToMemory&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;loadUserWorkspace&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;saveUserWorkspace&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="s2"&gt;@/lib/skill/workspace&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;Request&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="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userChatId&lt;/span&gt; &lt;span class="p"&gt;}&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;authenticate&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="c1"&gt;// Load skills from S3/disk&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;skills&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;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;agentSkill&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;userId&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;skillFiles&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;loadAllSkillsToMemory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;skills&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Load persisted workspace&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;workspaceFiles&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;loadUserWorkspace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Prefix skills for isolation&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;skillFilesWithPrefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromEntries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;skillFiles&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;`skills/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;path&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="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Create sandbox&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bashTools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sandbox&lt;/span&gt; &lt;span class="p"&gt;}&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;createBashTool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;files&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="nx"&gt;workspaceFiles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         &lt;span class="c1"&gt;// User's work (root directory)&lt;/span&gt;
      &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;skillFilesWithPrefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// Skills (skills/ subdirectory)&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Merge with other tools&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;baseTools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;              &lt;span class="c1"&gt;// webSearch, reasoningThinking, etc.&lt;/span&gt;
    &lt;span class="na"&gt;bash&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bashTools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;bash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bashTools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;writeFile&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;bashTools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeFile&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;exportFolder&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;exportFolderTool&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;sandbox&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="c1"&gt;// Stream with persistence&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;streamText&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;claude-sonnet-4-5&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;system&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;buildSystemPrompt&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;skills&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;locale&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;loadMessages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userChatId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;tools&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="nx"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="na"&gt;onStepFinish&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;saveStepToDB&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;trackTokens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;

    &lt;span class="na"&gt;onFinish&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Persist workspace changes&lt;/span&gt;
      &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;saveUserWorkspace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sandbox&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toUIMessageStreamResponse&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 points:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Load both skills and workspace at start&lt;/li&gt;
&lt;li&gt;Skills isolated under &lt;code&gt;skills/&lt;/code&gt; prefix (read-only)&lt;/li&gt;
&lt;li&gt;Workspace in root (read-write, persisted)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;onFinish&lt;/code&gt; saves all changes back to disk&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Workspace Persistence
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;File: &lt;code&gt;lib/skill/workspace.ts&lt;/code&gt;&lt;/strong&gt;&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="k"&gt;import&lt;/span&gt; &lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Sandbox&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="s2"&gt;bash-tool&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;loadUserWorkspace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;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;workspacePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getWorkspacePath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// .next/cache/sandbox/user/{id}/workspace&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="na"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&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="nf"&gt;loadDirectoryRecursively&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;workspacePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;files&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;files&lt;/span&gt;&lt;span class="p"&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;saveUserWorkspace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;sandbox&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Sandbox&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;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;workspacePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getWorkspacePath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Get all files except skills/&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;findResult&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;sandbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;executeCommand&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;`find . -type f ! -path "./skills/*" 2&amp;gt;/dev/null || echo ""`&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;filePaths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;findResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stdout&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="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Boolean&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Clear workspace and save fresh state&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;workspacePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;recursive&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;force&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mkdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;workspacePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;recursive&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="k"&gt;for &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;filePath&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;filePaths&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;content&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;sandbox&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&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;fullPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;workspacePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;\.\/&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&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;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mkdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fullPath&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;recursive&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="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fullPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;content&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Load&lt;/strong&gt;: Read all files from disk into memory&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent works&lt;/strong&gt;: Creates/modifies files in sandbox&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Save&lt;/strong&gt;: Extract non-skill files, write back to disk&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Next request&lt;/strong&gt;: Files reappear in sandbox&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  4. System Prompt
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;File: &lt;code&gt;app/prompt/index.ts&lt;/code&gt;&lt;/strong&gt;&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="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;buildUniversalSystemPrompt&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;locale&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;skills&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;skillsXml&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;skills&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;`
&amp;lt;skill&amp;gt;
  &amp;lt;name&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/name&amp;gt;
  &amp;lt;description&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;lt;/description&amp;gt;
  &amp;lt;location&amp;gt;skills/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/SKILL.md&amp;lt;/location&amp;gt;
&amp;lt;/skill&amp;gt;
  `&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`You are a Universal Agent with access to specialized skills.

## Available Skills

&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;skillsXml&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;

## Workspace Structure

&lt;/span&gt;&lt;span class="se"&gt;\`\`\`&lt;/span&gt;&lt;span class="s2"&gt;
sandbox/
├── skills/                 # Read-only skills from S3
│   ├── technical-writer/
│   └── market-researcher/
└── my-project/            # Your persistent workspace
    └── README.md
&lt;/span&gt;&lt;span class="se"&gt;\`\`\`&lt;/span&gt;&lt;span class="s2"&gt;

## How to Use Skills

1. **Load a skill**: &lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="s2"&gt;cat skills/technical-writer/SKILL.md&lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="s2"&gt;
2. **Embody the role**: Follow the skill's instructions completely
3. **Use skill references**: &lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="s2"&gt;cat skills/technical-writer/references/examples.md&lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="s2"&gt;

## File Operations

- **Create**: &lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="s2"&gt;writeFile({ path: "project/index.js", content: "..." })&lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="s2"&gt;
- **Read**: &lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="s2"&gt;cat project/index.js&lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="s2"&gt; or &lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="s2"&gt;readFile({ path: "project/index.js" })&lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="s2"&gt;
- **Export**: &lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="s2"&gt;exportFolder({ folderPath: "project" })&lt;/span&gt;&lt;span class="se"&gt;\`&lt;/span&gt;&lt;span class="s2"&gt; for user download

All files in root directory persist across conversations.

## Guidelines

- Load skills when user requests specialized work
- Follow skill instructions precisely—they're your expertise
- Create files in root directory (not under skills/)
- Use bash commands for exploration (ls, find, grep, etc.)`&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;Critical design:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Skills as XML (Claude-native format)&lt;/li&gt;
&lt;li&gt;Clear workspace vs. skills separation&lt;/li&gt;
&lt;li&gt;Emphasize persistence behavior&lt;/li&gt;
&lt;li&gt;Simple, actionable instructions&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Real-World Usage
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Example 1: Technical Documentation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User: "Write API documentation for our payment system"

Agent:
  1. cat skills/technical-writer/SKILL.md
  2. [Loads skill, embodies technical writer role]
  3. writeFile({ path: "docs/api-reference.md", content: "..." })
  4. writeFile({ path: "docs/examples.md", content: "..." })
  5. exportFolder({ folderPath: "docs" })

→ User downloads complete documentation package
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Example 2: Cross-Session Work
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User: "Research AI coding tools market, create report"

Agent:
  1. cat skills/market-researcher/SKILL.md
  2. webSearch({ query: "AI coding tools 2025 market analysis" })
  3. [Analyzes results, synthesizes insights]
  4. writeFile({ path: "research/market-analysis.md", content: "..." })
  5. [User stops conversation, goes to meeting]

Later:
User: "Add competitive landscape section"

Agent:
  1. cat research/market-analysis.md  # File still exists!
  2. [Continues work on existing file]
  3. writeFile({ path: "research/market-analysis.md", content: "..." })
  4. exportFolder({ folderPath: "research" })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Example 3: Agent Creates Own Skill
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User: "Create a skill for SQL query optimization"

Agent:
  1. mkdir -p sql-optimizer
  2. writeFile({ path: "sql-optimizer/SKILL.md", content: "..." })
  3. writeFile({ path: "sql-optimizer/references/patterns.md", content: "..." })
  4. exportFolder({ folderPath: "sql-optimizer" })

User: Downloads, uploads as .skill file
→ Now available in skills/ directory for future use
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Key Design Decisions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Why bash-tool?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Alternatives considered:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Direct filesystem access → Security risk&lt;/li&gt;
&lt;li&gt;Code interpreter (Python) → Too narrow&lt;/li&gt;
&lt;li&gt;Full VM (@vercel/sandbox) → Overkill for file operations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why bash-tool wins:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In-memory, isolated sandbox&lt;/li&gt;
&lt;li&gt;Familiar bash commands (ls, cat, grep, find)&lt;/li&gt;
&lt;li&gt;No script execution (security)&lt;/li&gt;
&lt;li&gt;Perfect for file manipulation + exploration&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Why Separate Skills and Workspace?
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✅ Current: skills/ (read-only) + workspace/ (read-write)
❌ Alternative: Everything in root directory

Problem: Agent might accidentally overwrite skills
Solution: Clear separation, explicit in prompts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Why Full Workspace Sync (Not Delta)?
&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;// ❌ Delta approach: Track and save only changed files&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;saveChangedFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;changedFiles&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Full sync: Save complete workspace state&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;saveEntireWorkspace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;allFiles&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;Reasoning:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simpler implementation (no change tracking needed)&lt;/li&gt;
&lt;li&gt;Handles deletions naturally (file missing → deleted)&lt;/li&gt;
&lt;li&gt;Performance: Workspace typically &amp;lt; 100 files, sync is fast&lt;/li&gt;
&lt;li&gt;Reliability: Avoids sync drift issues&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  4. Why Skills as Claude XML?
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;skill&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;name&amp;gt;&lt;/span&gt;technical-writer&lt;span class="nt"&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;description&amp;gt;&lt;/span&gt;Creates technical documentation&lt;span class="nt"&gt;&amp;lt;/description&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;location&amp;gt;&lt;/span&gt;skills/technical-writer/SKILL.md&lt;span class="nt"&gt;&amp;lt;/location&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/skill&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude models are trained on XML structures for tools and artifacts. This format:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Native to Claude's training&lt;/li&gt;
&lt;li&gt;Easy to parse&lt;/li&gt;
&lt;li&gt;Extensible (add metadata without breaking)&lt;/li&gt;
&lt;li&gt;Familiar to Claude for skill discovery&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Deployment Considerations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Docker Configuration
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Challenge:&lt;/strong&gt; bash-tool uses native modules (@mongodb-js/zstd, node-liblzma) for compression.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Don't copy native modules—they're optional.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="c"&gt;# Dockerfile&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder --chown=nextjs:nodejs /app/.next/standalone ./&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static&lt;/span&gt;

&lt;span class="c"&gt;# Note: Native compression modules NOT copied&lt;/span&gt;
&lt;span class="c"&gt;# - exportFolder uses jszip (pure JS)&lt;/span&gt;
&lt;span class="c"&gt;# - just-bash has JS fallback for compression&lt;/span&gt;
&lt;span class="c"&gt;# If you need tar -z in sandbox, uncomment:&lt;/span&gt;
&lt;span class="c"&gt;# COPY --from=deps /app/node_modules/.pnpm/@mongodb-js+zstd@*/node_modules/@mongodb-js ./node_modules/@mongodb-js&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;next.config.ts:&lt;/strong&gt;&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="nx"&gt;webpack&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;isServer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;webpack&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;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="nx"&gt;isServer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Only externalize native binaries&lt;/span&gt;
    &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;externals&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@mongodb-js/zstd&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node-liblzma&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Ignore browser-only worker.js&lt;/span&gt;
    &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;plugins&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;webpack&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;IgnorePlugin&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;resourceRegExp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/^&lt;/span&gt;&lt;span class="se"&gt;\.\/&lt;/span&gt;&lt;span class="sr"&gt;worker&lt;/span&gt;&lt;span class="se"&gt;\.&lt;/span&gt;&lt;span class="sr"&gt;js$/&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;contextRegExp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sr"&gt;/just-bash/&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="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;config&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;h3&gt;
  
  
  Storage Management
&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;// Periodic cleanup of old exports&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;cleanupOldExports&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;exportsDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&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="nf"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.next/cache/sandbox&lt;/span&gt;&lt;span class="dl"&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;cutoff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// 24 hours&lt;/span&gt;

  &lt;span class="k"&gt;for &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;userId&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;exportsDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;user&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;exportsPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;getExportsPath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Number&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="k"&gt;for &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;file&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;exportsPath&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;stat&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;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;exportsPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&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="nx"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mtimeMs&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;cutoff&lt;/span&gt;&lt;span class="p"&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;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;unlink&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;exportsPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Performance Benchmarks
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Tested on M1 Mac, 50 skills, 200 workspace files:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Operation&lt;/th&gt;
&lt;th&gt;Time&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Load skills + workspace&lt;/td&gt;
&lt;td&gt;120ms&lt;/td&gt;
&lt;td&gt;Parallel I/O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Create 10 files&lt;/td&gt;
&lt;td&gt;5ms&lt;/td&gt;
&lt;td&gt;In-memory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Save workspace&lt;/td&gt;
&lt;td&gt;80ms&lt;/td&gt;
&lt;td&gt;Write to disk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Export folder (50 files)&lt;/td&gt;
&lt;td&gt;150ms&lt;/td&gt;
&lt;td&gt;Zip creation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Optimization tips:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Cache skill files across requests (same user, same skills)&lt;/li&gt;
&lt;li&gt;Use streaming for large file exports&lt;/li&gt;
&lt;li&gt;Compress workspace with zstd if &amp;gt; 1MB&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;&lt;strong&gt;Extend the system:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Version control&lt;/strong&gt;: Git integration for workspace&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skill marketplace&lt;/strong&gt;: Share skills between users&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Collaborative workspaces&lt;/strong&gt;: Multiple agents, one workspace&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skill composition&lt;/strong&gt;: Agent loads multiple skills simultaneously&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skill inheritance&lt;/strong&gt;: Base skills + specialized variants&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The power:&lt;/strong&gt; Every user becomes a founder with a full engineering team. Each skill is a specialist. The Agent orchestrates. The workspace persists. The possibilities compound.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Main route&lt;/strong&gt;: &lt;code&gt;src/app/(universal)/api/chat/universal/route.ts&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Workspace persistence&lt;/strong&gt;: &lt;code&gt;src/lib/skill/workspace.ts&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skill loading&lt;/strong&gt;: &lt;code&gt;src/lib/skill/loadToMemory.ts&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Export tool&lt;/strong&gt;: &lt;code&gt;src/app/(universal)/tools/exportFolder.ts&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;System prompt&lt;/strong&gt;: &lt;code&gt;src/app/(universal)/prompt/index.ts&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;README&lt;/strong&gt;: &lt;code&gt;src/app/(universal)/README.md&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Built with Next.js 15, Vercel AI SDK, bash-tool, and Claude 4.5 Sonnet.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>programming</category>
    </item>
    <item>
      <title>Building Scalable AI Agent Systems: Three Evolutions</title>
      <dc:creator>web3nomad.eth</dc:creator>
      <pubDate>Sun, 11 Jan 2026 09:46:19 +0000</pubDate>
      <link>https://forem.com/web3nomad/building-scalable-ai-agent-systems-three-evolutions-3ahe</link>
      <guid>https://forem.com/web3nomad/building-scalable-ai-agent-systems-three-evolutions-3ahe</guid>
      <description>&lt;h2&gt;
  
  
  I. December 2025
&lt;/h2&gt;

&lt;p&gt;We needed to add a new feature to atypica.AI: group discussions (&lt;code&gt;discussionChat&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;This should've been simple. We already had &lt;code&gt;interviewChat&lt;/code&gt;—one-on-one conversations where users deeply engage with AI-simulated personas. Group discussion was just scaling from 1-to-1 to 1-to-many: 3-8 personas engaging simultaneously, watching perspectives collide and insights emerge.&lt;/p&gt;

&lt;p&gt;In theory, we just needed to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Reuse the interview logic&lt;/li&gt;
&lt;li&gt;Adjust prompts to simulate group dynamics&lt;/li&gt;
&lt;li&gt;Tweak the UI to show multiple speakers&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;The reality&lt;/strong&gt;: We had to modify 12 files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;prisma/schema.prisma          # New Discussion table
src/ai/tools/discussionChat.ts  # New tool
src/ai/tools/saveDiscussion.ts   # Save tool
src/app/(study)/agents/studyAgent.ts     # Add tool to agent
src/app/(study)/agents/fastInsightAgent.ts  # Add again
src/app/(study)/agents/productRnDAgent.ts   # And again
... 6 more files
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Worse, we discovered this:&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;// studyAgentRequest.ts (493 lines)&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;studyAgentRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&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;result&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;streamText&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;claude-sonnet-4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="na"&gt;system&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;studySystem&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;webSearch&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;interview&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;scoutTask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;saveAnalyst&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;generateReport&lt;/span&gt;
      &lt;span class="c1"&gt;// ... 15 tools&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;onStepFinish&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Save messages&lt;/span&gt;
      &lt;span class="c1"&gt;// Track tokens&lt;/span&gt;
      &lt;span class="c1"&gt;// Send notifications&lt;/span&gt;
      &lt;span class="c1"&gt;// ... 120 lines of logic&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;// fastInsightAgentRequest.ts (416 lines)&lt;/span&gt;
&lt;span class="c1"&gt;// 95% identical code&lt;/span&gt;

&lt;span class="c1"&gt;// productRnDAgentRequest.ts (302 lines)&lt;/span&gt;
&lt;span class="c1"&gt;// 95% identical code&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three nearly identical agent wrappers.&lt;br&gt;
Every new feature required copy-pasting across all three.&lt;br&gt;
Every bug fix meant changing it three times.&lt;/p&gt;

&lt;p&gt;That moment, we realized: &lt;strong&gt;something was fundamentally wrong.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not that our code wasn't elegant.&lt;br&gt;
Not that we lacked abstraction.&lt;br&gt;
But that we were building &lt;strong&gt;AI Agent systems&lt;/strong&gt; with &lt;strong&gt;traditional software engineering thinking&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This article chronicles how we escaped this trap—through three architectural evolutions, rethinking how AI Agents should be built from first principles.&lt;/p&gt;


&lt;h2&gt;
  
  
  II. Rethinking: What is an AI Agent?
&lt;/h2&gt;

&lt;p&gt;Before refactoring, we stopped to ask a fundamental question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's the essential difference between AI Agents and traditional software?&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  The World of Traditional Software
&lt;/h3&gt;

&lt;p&gt;Traditional software is built on &lt;strong&gt;state machines&lt;/strong&gt;:&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;class&lt;/span&gt; &lt;span class="nc"&gt;ResearchSession&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;IDLE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PLANNING&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;RESEARCHING&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;REPORTING&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;interviews&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Interview&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="nl"&gt;findings&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Finding&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="nl"&gt;reports&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Report&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;

  &lt;span class="nf"&gt;transition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;switch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;IDLE&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="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;START&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PLANNING&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PLANNING&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="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PLAN_COMPLETE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;RESEARCHING&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="c1"&gt;// ... more state transitions&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This model's core assumptions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;State is explicit&lt;/strong&gt;: I know exactly where I am&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transitions are deterministic&lt;/strong&gt;: Given state + event, next state is unique&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Control is precise&lt;/strong&gt;: if-else covers all paths&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This works beautifully for traditional software. But for AI Agents?&lt;/p&gt;

&lt;h3&gt;
  
  
  The World of AI Agents
&lt;/h3&gt;

&lt;p&gt;LLMs don't work this way:&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;messages&lt;/span&gt; &lt;span class="o"&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;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Want to understand young people&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="nx"&gt;coffee&lt;/span&gt; &lt;span class="nx"&gt;preferences&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; },
  { role: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="nx"&gt;assistant&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;, content: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="nx"&gt;I&lt;/span&gt; &lt;span class="nx"&gt;can&lt;/span&gt; &lt;span class="nx"&gt;help&lt;/span&gt; &lt;span class="nx"&gt;you&lt;/span&gt; &lt;span class="nx"&gt;conduct&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="nx"&gt;research&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; },
  { role: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="nx"&gt;assistant&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;, toolCalls: [{ name: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="nx"&gt;scoutTask&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;, args: {...} }] },
  { role: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="nx"&gt;tool&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;, content: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="nx"&gt;Observed&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="nx"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; },
  { role: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="nx"&gt;assistant&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;, content: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="nx"&gt;Based&lt;/span&gt; &lt;span class="nx"&gt;on&lt;/span&gt; &lt;span class="nx"&gt;observations&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;I&lt;/span&gt; &lt;span class="nx"&gt;suggest&lt;/span&gt; &lt;span class="nx"&gt;interviewing&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;25&lt;/span&gt; &lt;span class="nx"&gt;coffee&lt;/span&gt; &lt;span class="nx"&gt;enthusiasts&lt;/span&gt;&lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; },
  { role: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="nx"&gt;assistant&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;, toolCalls: [{ name: &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="nx"&gt;interviewChat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;, args: {...} }] },
  // ...
];
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where's the "state" here?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Not in a &lt;code&gt;state&lt;/code&gt; field&lt;/li&gt;
&lt;li&gt;But in the entire &lt;strong&gt;conversation history&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The AI infers from conversation history:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What research does the user want?&lt;/li&gt;
&lt;li&gt;How far have we progressed?&lt;/li&gt;
&lt;li&gt;What should happen next?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a completely different paradigm.&lt;/p&gt;

&lt;h3&gt;
  
  
  Three Core Insights
&lt;/h3&gt;

&lt;p&gt;From this observation, we derived three insights that shaped our architectural evolution.&lt;/p&gt;

&lt;h4&gt;
  
  
  Insight 1: Conversation as State
&lt;/h4&gt;

&lt;p&gt;Traditional approach: Maintain explicit state&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;// ❌ Traditional: Explicit state management&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ResearchState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;stage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;planning&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;researching&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;reporting&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;completedInterviews&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;pendingTasks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Need synchronization: state and conversation history can diverge&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;AI-native approach: Infer state from conversation&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;// ✅ AI-native: Conversation is state&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;conversationHistory&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="c1"&gt;// AI infers state from history, no explicit sync needed&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&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;streamText&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// AI knows what to do&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why is conversation superior to state machines?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Natural alignment&lt;/strong&gt;: LLMs work on message history natively&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strong fault tolerance&lt;/strong&gt;: State machines are hard to recover from errors; conversations can be "rewound" and replayed&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easy extension&lt;/strong&gt;: Adding new capabilities doesn't require modifying state graphs&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Insight 2: Reasoning-Execution Separation
&lt;/h4&gt;

&lt;p&gt;How humans make decisions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Understand intent&lt;/strong&gt;: "What am I trying to achieve?" → Clarify goals&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Choose method&lt;/strong&gt;: "How do I do it?" → Execution steps&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;AI Agents should follow the same pattern:&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;// Plan Mode: Understanding intent&lt;/span&gt;
&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;User says: want to understand young people's coffee preferences&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nx"&gt;Analyze&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;needs&lt;/span&gt; &lt;span class="nx"&gt;qualitative&lt;/span&gt; &lt;span class="nx"&gt;research&lt;/span&gt;
  &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nx"&gt;Decide&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;use&lt;/span&gt; &lt;span class="nx"&gt;group&lt;/span&gt; &lt;span class="nx"&gt;discussion&lt;/span&gt; &lt;span class="nx"&gt;method&lt;/span&gt;
  &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nx"&gt;Output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;complete&lt;/span&gt; &lt;span class="nx"&gt;research&lt;/span&gt; &lt;span class="nx"&gt;plan&lt;/span&gt;

&lt;span class="c1"&gt;// Study Agent: Executing plan&lt;/span&gt;
&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Received research plan&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nx"&gt;Call&lt;/span&gt; &lt;span class="nx"&gt;discussionChat&lt;/span&gt;
  &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nx"&gt;Analyze&lt;/span&gt; &lt;span class="nx"&gt;discussion&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt;
  &lt;span class="err"&gt;→&lt;/span&gt; &lt;span class="nx"&gt;Generate&lt;/span&gt; &lt;span class="nx"&gt;insights&lt;/span&gt; &lt;span class="nx"&gt;report&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why separate?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reasoning needs deep thinking (use Claude Sonnet 4)&lt;/li&gt;
&lt;li&gt;Execution needs fast response (can use smaller models)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Separation of concerns&lt;/strong&gt;, single responsibility&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Insight 3: Simple Over Precise
&lt;/h4&gt;

&lt;p&gt;Facing the "AI forgetfulness" problem, we could:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Option A: Vector DB + Semantic Search&lt;/strong&gt;&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;// Precise matching of relevant memories&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;query_embedding&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;embed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user_message&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;relevant_memories&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;vectorDB&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;query_embedding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;top_k&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;✅ Precise retrieval&lt;/li&gt;
&lt;li&gt;❌ Requires embedding, indexing, complex queries&lt;/li&gt;
&lt;li&gt;❌ High maintenance cost&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Option B: Markdown Files + Full Loading&lt;/strong&gt;&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;// Simple and transparent&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;memory&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;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`memories/&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.md`&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;messages&lt;/span&gt; &lt;span class="o"&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;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;UserMemory&amp;gt;\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n&amp;lt;/UserMemory&amp;gt;`&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;conversationMessages&lt;/span&gt;
&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;✅ Simple, transparent, user-editable&lt;/li&gt;
&lt;li&gt;✅ Leverages large context windows (Claude 200K tokens)&lt;/li&gt;
&lt;li&gt;✅ Easier to debug and understand&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We chose Option B.&lt;/p&gt;

&lt;p&gt;Why?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Context windows changed the game&lt;/strong&gt;: User memory typically &amp;lt; 10K, full loading is perfectly viable&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simple solutions are more reliable&lt;/strong&gt;: No embedding inconsistency, no retrieval failures&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User control&lt;/strong&gt;: Memory is transparent, users can view and edit&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Four Design Principles
&lt;/h3&gt;

&lt;p&gt;From these three insights, we distilled the core principles of our architecture:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Messages as Source of Truth&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All important information lives in messages&lt;/li&gt;
&lt;li&gt;Database only stores derived state (like reports, study logs)&lt;/li&gt;
&lt;li&gt;Similar to Event Sourcing: messages are the event log&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Configuration over Code&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use configuration to express differences&lt;/li&gt;
&lt;li&gt;Use code to express commonalities&lt;/li&gt;
&lt;li&gt;Avoid over-abstraction&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. AI as State Manager&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Let AI manage state transitions&lt;/li&gt;
&lt;li&gt;Don't hand-write complex state machines&lt;/li&gt;
&lt;li&gt;Adapt to LLM's capability boundaries&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. Simple, Transparent, Controllable&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simple beats complex&lt;/li&gt;
&lt;li&gt;Transparent beats black box&lt;/li&gt;
&lt;li&gt;User control beats AI automation&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  III. Step 1: Message-Driven Architecture
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;v2.2.0 - 2025-12-27&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem: Dual Source of Truth
&lt;/h3&gt;

&lt;p&gt;Initially, research data was scattered across three places:&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;// Place 1: analyst table&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;analyst&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;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;analyst&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findUnique&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;analyst&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;studySummary&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// "Research summary..."&lt;/span&gt;

&lt;span class="c1"&gt;// Place 2: interviews table&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;interviews&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;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;interview&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;analystId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;interviews&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;conclusion&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;  &lt;span class="c1"&gt;// ["Interview 1 conclusion", "Interview 2 conclusion"]&lt;/span&gt;

&lt;span class="c1"&gt;// Place 3: messages table&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;messages&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;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chatMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;userChatId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// webSearch results are here&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generating reports required stitching from three places:&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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;generateReport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;analystId&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;analyst&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;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;analyst&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findUnique&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;analystId&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;include&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;interviews&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="c1"&gt;// JOIN!&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;messages&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;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;chatMessage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findMany&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;userChatId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;analyst&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;studyUserChatId&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="c1"&gt;// Stitch data together&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reportData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;analyst&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;studySummary&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;              &lt;span class="c1"&gt;// from analyst table&lt;/span&gt;
    &lt;span class="na"&gt;interviewInsights&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;analyst&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;interviews&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(...),&lt;/span&gt;  &lt;span class="c1"&gt;// from interviews table&lt;/span&gt;
    &lt;span class="na"&gt;webResearch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;extractFromMessages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;    &lt;span class="c1"&gt;// from messages table&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;Problems&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Data inconsistency&lt;/strong&gt;: &lt;code&gt;interviews.conclusion&lt;/code&gt; and interview content in messages could diverge&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Partial failures&lt;/strong&gt;: When tool calls fail, data is half-saved, hard to trace full context&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hard to extend&lt;/strong&gt;: Adding &lt;code&gt;discussionChat&lt;/code&gt; requires new table, new tool, new queries&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Even worse, tool outputs were inconsistent:&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;// interviewChat: content in DB, returns reference&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;toolName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;interviewChat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;interviewId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;  &lt;span class="c1"&gt;// Need another DB query&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// scoutTaskChat: content in return value&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;toolName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;scoutTaskChat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;plainText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Observation results...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Content directly returned&lt;/span&gt;
    &lt;span class="na"&gt;insights&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agents couldn't handle this uniformly, leading to complex code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Solution: Messages as Single Source
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Core idea&lt;/strong&gt;: All research content flows into the message stream. Database only stores derived state.&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;// ✅ New architecture: Unified output format&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ResearchToolResult&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;plainText&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="c1"&gt;// Human-readable summary, required&lt;/span&gt;
  &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&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="kr"&gt;any&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Optional structured data&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// interviewChat also returns plainText&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;toolName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;interviewChat&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;output&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;plainText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Interview summary: User Zhang San mentioned...&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// ← Full content here&lt;/span&gt;
    &lt;span class="na"&gt;interviewId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;123&lt;/span&gt;  &lt;span class="c1"&gt;// Optional: DB reference&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 changes&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Removed 5 specialized save tools&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deleted: &lt;code&gt;saveInterview&lt;/code&gt;, &lt;code&gt;saveDiscussion&lt;/code&gt;, &lt;code&gt;saveScoutTask&lt;/code&gt;, ...&lt;/li&gt;
&lt;li&gt;Reason: Agents output directly to messages, no explicit save needed&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Unified tool output format&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;All research tools return &lt;code&gt;plainText&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Agents can uniformly process all tool results&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Generate studyLog on demand&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;   &lt;span class="c1"&gt;// Don't pre-save, generate when needed&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;analyst&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;studyLog&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;messages&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;loadMessages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;studyUserChatId&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;studyLog&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;generateStudyLog&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;  &lt;span class="c1"&gt;// ← Generate from messages&lt;/span&gt;
     &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;prisma&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;analyst&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
       &lt;span class="na"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
       &lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;studyLog&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why This Design?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Reasoning from first principles&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Conversation as context&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;LLMs need complete context to generate reports&lt;/li&gt;
&lt;li&gt;Message history is naturally the most complete, most natural context&lt;/li&gt;
&lt;li&gt;Avoids complexity of "reconstructing context from DB"&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;LLMs excel at extraction&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generating structured content (studyLog) from conversations is LLM's strength&lt;/li&gt;
&lt;li&gt;More flexible and reliable than hand-written parsing logic&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Shadow of Event Sourcing&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Message sequence = event log&lt;/li&gt;
&lt;li&gt;studyLog, report = derived state&lt;/li&gt;
&lt;li&gt;Can be replayed and regenerated anytime&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Comparison with other approaches&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Pros&lt;/th&gt;
&lt;th&gt;Cons&lt;/th&gt;
&lt;th&gt;Why not chosen&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Messages as source&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Data consistent, easy to extend&lt;/td&gt;
&lt;td&gt;Requires extra LLM call to generate studyLog&lt;/td&gt;
&lt;td&gt;✅ Our choice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Traditional state management&lt;/td&gt;
&lt;td&gt;Precise control&lt;/td&gt;
&lt;td&gt;Complex state sync, hard to trace&lt;/td&gt;
&lt;td&gt;Doesn't suit LLM non-determinism&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Remove DB entirely&lt;/td&gt;
&lt;td&gt;Extremely simple&lt;/td&gt;
&lt;td&gt;Frontend queries difficult, history hard to manage&lt;/td&gt;
&lt;td&gt;Need structured display&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Event Sourcing&lt;/td&gt;
&lt;td&gt;Complete history, replayable&lt;/td&gt;
&lt;td&gt;High engineering complexity&lt;/td&gt;
&lt;td&gt;Over-engineered for current scale&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Impact
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Code simplification&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight diff"&gt;&lt;code&gt;&lt;span class="p"&gt;Deleted files:
&lt;/span&gt;&lt;span class="gd"&gt;- src/ai/tools/saveInterview.ts
- src/ai/tools/saveDiscussion.ts
- src/ai/tools/saveScoutTask.ts
- src/ai/tools/savePersona.ts
- src/ai/tools/saveWebSearch.ts
&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;span class="p"&gt;Simplified files (28):
&lt;/span&gt;&lt;span class="gd"&gt;- Agent configs no longer need save tools
- generateReport doesn't need multi-table JOINs
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Development efficiency&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;Before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Adding discussionChat:
1. Create Discussion table
2. Write discussionChat tool
3. Write saveDiscussion tool
4. Add both tools to 3 agents
5. Write discussion query logic
6. Modify generateReport query

Total: 12 files, 2-3 days
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Adding discussionChat:
1. Write discussionChat tool (returns plainText)
2. Add tool to agent config
3. generateReport auto-supports (reads from messages)

Total: 3 files, 2-3 hours
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Cost trade-offs&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;✅ &lt;strong&gt;Benefits&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simplified architecture: deleted 5 tools, simplified 28 files&lt;/li&gt;
&lt;li&gt;Data consistency: full context traceable even on failures&lt;/li&gt;
&lt;li&gt;Easy extension: adding new research methods goes from 12 steps → 3 steps&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;❌ &lt;strong&gt;Costs&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;studyLog generation requires extra LLM call (~2K tokens, ~$0.002)&lt;/li&gt;
&lt;li&gt;Slightly higher token consumption for long conversations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;✅ &lt;strong&gt;Mitigation&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prompt cache reduces repeated token cost by 90%&lt;/li&gt;
&lt;li&gt;Architectural benefits far outweigh costs&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  III. Step 2: Intent Clarification + Unified Execution
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;v2.3.0 - 2026-01-06&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem 1: Vague Requirements → Inefficient Dialogue
&lt;/h3&gt;

&lt;p&gt;After implementing message-driven architecture, adding features became simpler. But user experience wasn't good enough.&lt;/p&gt;

&lt;p&gt;When creating research, users often say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Want to understand young people's coffee preferences"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This isn't specific enough:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Which young people&lt;/strong&gt;? 18-22 college students? Or 23-28 young professionals?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What method&lt;/strong&gt;? In-depth interviews? Group discussions? Or social media observation?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What output&lt;/strong&gt;? User personas? Market insights? Or product recommendations?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Traditional approach: AI asks multiple questions&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AI: "Which age group do you want to research?"
User: "18-25 I guess"
AI: "What method? Interviews or surveys?"
User: "Interviews"
AI: "How many people?"
User: "Around 10"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;Requires 3-5 conversation rounds&lt;/li&gt;
&lt;li&gt;Poor user experience (feels like filling forms)&lt;/li&gt;
&lt;li&gt;AI can't proactively suggest best approaches&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Problem 2: 95% Duplicate Code
&lt;/h3&gt;

&lt;p&gt;While adding features became simpler, we discovered a bigger technical debt:&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="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; src/app/&lt;span class="o"&gt;(&lt;/span&gt;study&lt;span class="o"&gt;)&lt;/span&gt;/agents/&lt;span class="k"&gt;*&lt;/span&gt;AgentRequest.ts
493 studyAgentRequest.ts
416 fastInsightAgentRequest.ts
302 productRnDAgentRequest.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three nearly identical agent wrappers, totaling &lt;strong&gt;1,211 lines&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Code duplication mainly in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Message loading and processing (~80 lines each)&lt;/li&gt;
&lt;li&gt;File attachment handling (~60 lines each)&lt;/li&gt;
&lt;li&gt;MCP integration (~40 lines each)&lt;/li&gt;
&lt;li&gt;Token tracking (~50 lines each)&lt;/li&gt;
&lt;li&gt;Notification sending (~30 lines each)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every new feature (like webhook integration) required changing all three places.&lt;/p&gt;

&lt;h3&gt;
  
  
  Solution: Plan Mode + baseAgentRequest
&lt;/h3&gt;

&lt;p&gt;Our solution has two parts:&lt;/p&gt;

&lt;h4&gt;
  
  
  Part 1: Plan Mode (Intent Clarification Layer)
&lt;/h4&gt;

&lt;p&gt;A separate agent dedicated to intent clarification:&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;// src/app/(study)/agents/configs/planModeAgentConfig.ts&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;createPlanModeAgentConfig&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="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;claude-sonnet-4-5&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;systemPrompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;planModeSystem&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;locale&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;requestInteraction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Interact with user&lt;/span&gt;
      &lt;span class="nx"&gt;makeStudyPlan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;       &lt;span class="c1"&gt;// Display complete plan, one-click confirm&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;maxSteps&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// Max 5 steps to complete clarification&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;Workflow&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;sequenceDiagram
    participant User
    participant PlanMode as Plan Mode Agent
    participant StudyAgent as Study Agent

    User-&amp;gt;&amp;gt;PlanMode: "Want to understand young people's coffee preferences"

    PlanMode-&amp;gt;&amp;gt;PlanMode: Analyze requirements
    Note over PlanMode: - Target: 18-25 years old&amp;lt;br/&amp;gt;- Research type: qualitative insights&amp;lt;br/&amp;gt;- Best method: group discussion

    PlanMode-&amp;gt;&amp;gt;User: Display complete plan
    Note over PlanMode,User: 【Research Plan】&amp;lt;br/&amp;gt;Goal: Understand 18-25 coffee preferences&amp;lt;br/&amp;gt;Method: Group discussion (5-8 people)&amp;lt;br/&amp;gt;Duration: ~40 minutes&amp;lt;br/&amp;gt;Output: Consumer insights report&amp;lt;br/&amp;gt;&amp;lt;br/&amp;gt;[Confirm Start] [Modify Plan]

    User-&amp;gt;&amp;gt;PlanMode: [Confirm Start]

    PlanMode-&amp;gt;&amp;gt;StudyAgent: Intent recorded in messages
    Note over StudyAgent: Read intent from conversation history&amp;lt;br/&amp;gt;Execute research plan

    StudyAgent-&amp;gt;&amp;gt;User: Start research execution
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;Plan Mode's decisions are recorded in messages&lt;/li&gt;
&lt;li&gt;Study Agent infers intent from messages, no explicit passing needed&lt;/li&gt;
&lt;li&gt;Avoids complexity of context passing&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Part 2: baseAgentRequest (Unified Executor)
&lt;/h4&gt;

&lt;p&gt;Merge three duplicate agent wrappers into one generic executor:&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;// src/app/(study)/agents/baseAgentRequest.ts (577 lines)&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;AgentRequestConfig&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TOOLS&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;ToolSet&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LLMModelName&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;systemPrompt&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="nl"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;TOOLS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;maxSteps&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;specialHandlers&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Dynamically control which tools are available&lt;/span&gt;
    &lt;span class="nx"&gt;customPrepareStep&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="nx"&gt;activeTools&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kr"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;TOOLS&lt;/span&gt;&lt;span class="p"&gt;)[]&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="c1"&gt;// Custom post-processing logic&lt;/span&gt;
    &lt;span class="nl"&gt;customOnStepFinish&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="o"&gt;&amp;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="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;executeBaseAgentRequest&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TOOLS&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;baseContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;BaseAgentContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;AgentRequestConfig&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;TOOLS&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;streamWriter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;UIMessageStreamWriter&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Phase 1: Initialization&lt;/span&gt;
  &lt;span class="c1"&gt;// Phase 2: Prepare Messages&lt;/span&gt;
  &lt;span class="c1"&gt;// Phase 3: Universal Attachment Processing&lt;/span&gt;
  &lt;span class="c1"&gt;// Phase 4: Universal MCP and Team System Prompt&lt;/span&gt;
  &lt;span class="c1"&gt;// Phase 5: Load Memory and Inject into Context&lt;/span&gt;
  &lt;span class="c1"&gt;// Phase 6: Main Streaming Loop&lt;/span&gt;
  &lt;span class="c1"&gt;// Phase 7: Universal Notifications&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;Agent routing&lt;/strong&gt;:&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;// src/app/(study)/api/chat/route.ts&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;analyst&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Plan Mode - intent clarification&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&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;createPlanModeAgentConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;agentContext&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;executeBaseAgentRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;agentContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;streamWriter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;analyst&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kind&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;AnalystKind&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;productRnD&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Product R&amp;amp;D Agent&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&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;createProductRnDAgentConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;agentContext&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;executeBaseAgentRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;agentContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;streamWriter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Study Agent (comprehensive research, fast insights, testing, creative, etc.)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;config&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;createStudyAgentConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;agentContext&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;executeBaseAgentRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;agentContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;streamWriter&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;Each agent only needs to define configuration&lt;/strong&gt;:&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;// src/app/(study)/agents/configs/studyAgentConfig.ts&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;createStudyAgentConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&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="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;claude-sonnet-4&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;systemPrompt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;studySystem&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;locale&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;buildStudyTools&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;params&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;// ← Tools this agent needs&lt;/span&gt;

    &lt;span class="na"&gt;specialHandlers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Custom tool control&lt;/span&gt;
      &lt;span class="na"&gt;customPrepareStep&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;toolUseCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;calculateToolUsage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;messages&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;activeTools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="c1"&gt;// After report generation, restrict available tools&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;toolUseCount&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;ToolName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;generateReport&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;activeTools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
            &lt;span class="nx"&gt;ToolName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;generateReport&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;ToolName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reasoningThinking&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nx"&gt;ToolName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toolCallError&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="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;activeTools&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;

      &lt;span class="c1"&gt;// Custom post-processing&lt;/span&gt;
      &lt;span class="na"&gt;customOnStepFinish&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// After saving research intent, auto-generate title&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;saveAnalystTool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;findTool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ToolName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;saveAnalyst&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="nx"&gt;saveAnalystTool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;generateChatTitle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;studyUserChatId&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="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why This Design?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Reasoning-execution separation rationale&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Matches cognitive model&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Human decision-making: first figure out "what to do", then consider "how to do it"&lt;/li&gt;
&lt;li&gt;System 1 (intuition) vs System 2 (reasoning)&lt;/li&gt;
&lt;li&gt;Plan Mode = System 2, Study Agent = System 1&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Single responsibility&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Plan Mode: focuses on intent understanding, doesn't need to know execution details&lt;/li&gt;
&lt;li&gt;Study Agent: focuses on research execution, doesn't need to handle clarification&lt;/li&gt;
&lt;li&gt;Each is simpler and easier to maintain&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Messages as protocol&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Plan Mode's decisions → messages&lt;/li&gt;
&lt;li&gt;Study Agent reads intent from messages&lt;/li&gt;
&lt;li&gt;Loosely coupled without losing context&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Unified executor rationale&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Extract, Don't Rebuild&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Extract common patterns from three similar implementations&lt;/li&gt;
&lt;li&gt;Not designing abstraction layer from scratch&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Configuration over Inheritance&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Agent differences expressed through configuration&lt;/li&gt;
&lt;li&gt;No inheritance or polymorphism&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Plugin-based Lifecycle&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;customPrepareStep&lt;/code&gt;: dynamic tool control&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;customOnStepFinish&lt;/code&gt;: custom post-processing&lt;/li&gt;
&lt;li&gt;Preserve extension points, don't hard-code all logic&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Comparison with other approaches&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Pros&lt;/th&gt;
&lt;th&gt;Cons&lt;/th&gt;
&lt;th&gt;Why not chosen&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Plan Mode + baseAgentRequest&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Remove duplicate code, separate reasoning-execution&lt;/td&gt;
&lt;td&gt;One more abstraction layer&lt;/td&gt;
&lt;td&gt;✅ Our choice&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Continue copy-pasting&lt;/td&gt;
&lt;td&gt;Simple and direct&lt;/td&gt;
&lt;td&gt;Tech debt accumulates, hard to maintain&lt;/td&gt;
&lt;td&gt;Unsustainable long-term&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fully generic agent&lt;/td&gt;
&lt;td&gt;Least code&lt;/td&gt;
&lt;td&gt;Sacrifices specialization and control&lt;/td&gt;
&lt;td&gt;Can't handle business differences&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Microservices split&lt;/td&gt;
&lt;td&gt;Independent deployment&lt;/td&gt;
&lt;td&gt;Over-engineered, adds ops complexity&lt;/td&gt;
&lt;td&gt;Unnecessary at current scale&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Impact
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Code complexity&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;Deleted:
- studyAgentRequest.ts &lt;span class="o"&gt;(&lt;/span&gt;493 lines&lt;span class="o"&gt;)&lt;/span&gt;
- fastInsightAgentRequest.ts &lt;span class="o"&gt;(&lt;/span&gt;416 lines&lt;span class="o"&gt;)&lt;/span&gt;
- productRnDAgentRequest.ts &lt;span class="o"&gt;(&lt;/span&gt;302 lines&lt;span class="o"&gt;)&lt;/span&gt;
Total: &lt;span class="nt"&gt;-1&lt;/span&gt;,211 lines

Added:
+ baseAgentRequest.ts &lt;span class="o"&gt;(&lt;/span&gt;577 lines&lt;span class="o"&gt;)&lt;/span&gt;
+ planModeAgentConfig.ts &lt;span class="o"&gt;(&lt;/span&gt;120 lines&lt;span class="o"&gt;)&lt;/span&gt;
+ studyAgentConfig.ts &lt;span class="o"&gt;(&lt;/span&gt;180 lines&lt;span class="o"&gt;)&lt;/span&gt;
+ productRnDAgentConfig.ts &lt;span class="o"&gt;(&lt;/span&gt;80 lines&lt;span class="o"&gt;)&lt;/span&gt;
Total: +957 lines

Net reduction: &lt;span class="nt"&gt;-254&lt;/span&gt; lines
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But more importantly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cyclomatic Complexity: &lt;strong&gt;12.3 → 6.7&lt;/strong&gt; (45% reduction)&lt;/li&gt;
&lt;li&gt;Code duplication: &lt;strong&gt;95% → 0%&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Development efficiency&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;Before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Adding MCP integration:
1. Modify studyAgentRequest.ts
2. Modify fastInsightAgentRequest.ts
3. Modify productRnDAgentRequest.ts
4. Test three agents

Time: 2-3 days
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Adding MCP integration:
1. Modify baseAgentRequest.ts
2. All agents automatically gain new capability

Time: 2-3 hours
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;User experience&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;Before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User: "Want to understand young people's coffee preferences"
AI: "Which age group do you want to research?"
User: "18-25"
AI: "What method do you want to use?"
User: "Interviews I guess"
AI: "How many people?"
... (3-5 conversation rounds)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User: "Want to understand young people's coffee preferences"
AI displays complete plan:
┌─────────────────────────────────────┐
│ 【Research Plan】                   │
│ Goal: Understand 18-25 coffee prefs │
│ Method: Group discussion (5-8 ppl)  │
│ Duration: ~40 minutes               │
│ Output: Consumer insights report    │
│                                     │
│ [Confirm Start] [Modify Plan]       │
└─────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Intent clarification: &lt;strong&gt;3-5 conversation rounds → 1 confirmation&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  III. Step 3: Persistent Memory
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;v2.3.0 - 2026-01-08&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem: AI "Amnesia"
&lt;/h3&gt;

&lt;p&gt;With intent clarification and unified architecture, the research workflow was smooth. But long-term users reported a problem:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Why does the AI ask me what industry I'm in every single time?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The AI doesn't remember users. Every conversation feels like the first meeting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"What industry are you in?"&lt;/li&gt;
&lt;li&gt;"Which dimensions do you care about?"&lt;/li&gt;
&lt;li&gt;"What's your research goal?"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Users feel the AI is "forgetful", the experience lacks personalization.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Root cause&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;LLMs are stateless. Each conversation:&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;result&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;streamText&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;currentConversation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// ← Only current conversation&lt;/span&gt;
  &lt;span class="c1"&gt;// No context from historical conversations&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Although we have historical conversations in the DB:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Cross-conversation info lost&lt;/strong&gt;: Each research is an independent session&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Important info buried&lt;/strong&gt;: Key information in long conversations is hard to extract&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No persistent memory&lt;/strong&gt;: No long-term memory of "who the user is"&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Solution: Two-Tier Memory Architecture
&lt;/h3&gt;

&lt;p&gt;We need a persistent memory system. But how to design it?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Inspired by Anthropic's CLAUDE.md approach&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simple Markdown files&lt;/li&gt;
&lt;li&gt;User-viewable and editable&lt;/li&gt;
&lt;li&gt;Fully loaded into context&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We adopted a similar approach but added automatic update mechanisms.&lt;/p&gt;

&lt;h4&gt;
  
  
  Data Model
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;model Memory {
  id      Int  @id @default(autoincrement())
  userId  Int? // User-level memory
  teamId  Int? // Team-level memory
  version Int  // Version management

  // Two-tier architecture
  core    String @default("") @db.Text  // Core memory (Markdown)
  working Json   @default("[]")         // Working memory (JSON, to be consolidated)

  changeNotes String @db.Text  // Update notes

  @@unique([userId, version])
  @@index([userId, version(sort: Desc)])
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Two-tier architecture&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Core Memory (core)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Markdown format, human-readable&lt;/li&gt;
&lt;li&gt;Long-term stable user information&lt;/li&gt;
&lt;li&gt;Example:
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt; # User Information
&lt;span class="p"&gt; -&lt;/span&gt; Industry: Consumer goods product manager
&lt;span class="p"&gt; -&lt;/span&gt; Focus: Young consumer preferences, emerging trends

 # Research Style
&lt;span class="p"&gt; -&lt;/span&gt; Prefers qualitative research (interviews, discussions)
&lt;span class="p"&gt; -&lt;/span&gt; Values authentic user voices over statistics
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Working Memory (working)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;JSON format, structured&lt;/li&gt;
&lt;li&gt;New information to be consolidated&lt;/li&gt;
&lt;li&gt;Example:
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="highlight json"&gt;&lt;code&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"info"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"User recently focused on coffee market"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"chat_123"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"info"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Prefers group discussion method"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"chat_124"&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="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Automatic Update Mechanism
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Two-stage update&lt;/strong&gt;:&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;// src/app/(memory)/actions.ts&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;updateMemory&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;conversationContext&lt;/span&gt; &lt;span class="p"&gt;})&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;memory&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;loadLatestMemory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Step 1: Reorganize when threshold exceeded (Claude Sonnet 4.5)&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;core&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;8000&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;working&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;memory&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;reorganizeMemory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;conversationContext&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: Extract new information (Claude Haiku 4.5)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newInfo&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;extractMemoryUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;core&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;conversationContext&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="nx"&gt;newInfo&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: Insert new information at specified location&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;insertMemoryInfo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;newInfo&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Memory Update Agent (Haiku 4.5)&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Extract new user information from conversations&lt;/li&gt;
&lt;li&gt;Low cost (~$0.001/time)&lt;/li&gt;
&lt;li&gt;Runs in background after each conversation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Memory Reorganize Agent (Sonnet 4.5)&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Consolidate working memory into core memory&lt;/li&gt;
&lt;li&gt;Remove redundancy, merge similar information&lt;/li&gt;
&lt;li&gt;Slightly higher cost (~$0.02/time), but infrequently triggered&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Integration into Conversation Flow
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// src/app/(study)/agents/baseAgentRequest.ts&lt;/span&gt;

&lt;span class="c1"&gt;// Phase 5: Load Memory&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;memory&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;loadUserMemory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&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="nx"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;core&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Inject at conversation start&lt;/span&gt;
  &lt;span class="nx"&gt;modelMessages&lt;/span&gt; &lt;span class="o"&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;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`&amp;lt;UserMemory&amp;gt;\n&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;core&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;\n&amp;lt;/UserMemory&amp;gt;`&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;modelMessages&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Phase 6: Streaming&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&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;streamText&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;modelMessages&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c1"&gt;// ← Includes user memory&lt;/span&gt;
  &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Phase 7: Non-blocking memory update&lt;/span&gt;
&lt;span class="nf"&gt;waitUntil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nf"&gt;updateMemory&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;conversationContext&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;messages&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;h3&gt;
  
  
  Why This Design?
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Why Markdown over Vector DB?&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Context window is large enough&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Claude 3.5 Sonnet: 200K tokens&lt;/li&gt;
&lt;li&gt;User memory typically &amp;lt; 10K characters (~3K tokens)&lt;/li&gt;
&lt;li&gt;Full loading is simpler and more accurate than retrieval&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Simple and transparent&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Markdown is user-readable and editable&lt;/li&gt;
&lt;li&gt;No embeddings, no vector search, no complex indexing&lt;/li&gt;
&lt;li&gt;Aligns with Anthropic's philosophy: user control&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Avoid premature optimization&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Don't need real-time retrieval (low conversation frequency)&lt;/li&gt;
&lt;li&gt;Don't need precise matching (full text provides enough context)&lt;/li&gt;
&lt;li&gt;Start with simple solution, optimize when necessary&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Comparison with mainstream approaches&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Storage&lt;/th&gt;
&lt;th&gt;Control&lt;/th&gt;
&lt;th&gt;Retrieval&lt;/th&gt;
&lt;th&gt;atypica choice rationale&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Anthropic (CLAUDE.md)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;File-based&lt;/td&gt;
&lt;td&gt;User-driven&lt;/td&gt;
&lt;td&gt;Full loading&lt;/td&gt;
&lt;td&gt;✅ Simple, transparent, effective with large context&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;OpenAI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Vector DB (speculated)&lt;/td&gt;
&lt;td&gt;AI + user confirmation&lt;/td&gt;
&lt;td&gt;Semantic retrieval&lt;/td&gt;
&lt;td&gt;❌ Black box, weak user control&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mem0&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Vector + Graph + KV&lt;/td&gt;
&lt;td&gt;AI-driven&lt;/td&gt;
&lt;td&gt;Hybrid retrieval&lt;/td&gt;
&lt;td&gt;❌ Over-engineered, high maintenance cost&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;MemGPT&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;OS-inspired tiered&lt;/td&gt;
&lt;td&gt;AI self-managed&lt;/td&gt;
&lt;td&gt;Tiered retrieval&lt;/td&gt;
&lt;td&gt;❌ Conceptually complex, utility unproven&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We chose &lt;strong&gt;Anthropic's simple approach&lt;/strong&gt; because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fits current scale (personal assistant, not enterprise knowledge base)&lt;/li&gt;
&lt;li&gt;User controllable (transparent, editable)&lt;/li&gt;
&lt;li&gt;As context windows grow, this approach becomes better&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Impact
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;User experience&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;Before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;First conversation:
User: "Want to do coffee research"
AI: "What industry are you in?"
User: "Consumer goods"
AI: "What dimensions do you care about?"
...

Second conversation (a week later):
User: "Want to do tea beverage research"
AI: "What industry are you in?"  # ← Asks again
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;First conversation:
User: "Want to do coffee research"
AI: "What industry are you in?"
User: "Consumer goods product manager"
# AI remembers

Second conversation (a week later):
User: "Want to do tea beverage research"
AI: "Based on your background as a consumer goods PM, I suggest..."  # ← Remembers!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;System cost&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;Memory Update (per conversation):
- Model: Claude Haiku 4.5
- Tokens: ~5K
- Cost: ~$0.001

Memory Reorganize (every 20 conversations):
- Model: Claude Sonnet 4.5
- Tokens: ~15K
- Cost: ~$0.02

Average cost: ~$0.002/conversation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Response time&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;Memory loading: +50ms (non-blocking)
Memory update: background, doesn't affect response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Low cost, fast response, completely acceptable.&lt;/p&gt;




&lt;h2&gt;
  
  
  IV. Architecture Comparison: Our Unique Choices
&lt;/h2&gt;

&lt;p&gt;Now let's step back and see how atypica's architecture differs from mainstream AI Agent frameworks.&lt;/p&gt;

&lt;h3&gt;
  
  
  State Management: Messages vs Memory Classes
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;atypica&lt;/th&gt;
&lt;th&gt;LangChain&lt;/th&gt;
&lt;th&gt;Core Difference&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Messages as source&lt;/td&gt;
&lt;td&gt;ConversationBufferMemory&lt;/td&gt;
&lt;td&gt;We believe &lt;strong&gt;conversation history is the best state&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Generate studyLog on demand&lt;/td&gt;
&lt;td&gt;Pre-compute summary&lt;/td&gt;
&lt;td&gt;Avoid sync issues, traceable on failures&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DB stores derived state&lt;/td&gt;
&lt;td&gt;DB stores core state&lt;/td&gt;
&lt;td&gt;Similar to Event Sourcing&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Why different?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;LangChain's design is influenced by traditional software, believing "state should be explicitly stored and managed."&lt;/p&gt;

&lt;p&gt;We believe, for LLMs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Conversation history = complete state&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Derived state (studyLog) can be regenerated&lt;/li&gt;
&lt;li&gt;Simpler, more fault-tolerant&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Agent Architecture: Configuration vs Graph
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;atypica&lt;/th&gt;
&lt;th&gt;LangGraph&lt;/th&gt;
&lt;th&gt;Core Difference&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Configuration-driven&lt;/td&gt;
&lt;td&gt;Graph-driven&lt;/td&gt;
&lt;td&gt;We use &lt;strong&gt;configuration to express differences, code for commonalities&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Single executor&lt;/td&gt;
&lt;td&gt;Node orchestration&lt;/td&gt;
&lt;td&gt;Avoid over-abstraction, good enough is enough&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Messages as protocol&lt;/td&gt;
&lt;td&gt;Explicit node communication&lt;/td&gt;
&lt;td&gt;Loosely coupled without losing context&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Why different?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;LangGraph pursues generality, using graph orchestration to express arbitrarily complex flows.&lt;/p&gt;

&lt;p&gt;We believe, for our scenarios:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Configuration-driven is simpler&lt;/strong&gt;: 99% of needs can be met with configuration&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Single executor is sufficient&lt;/strong&gt;: Don't need graph orchestration's flexibility&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simpler is more reliable&lt;/strong&gt;: Fewer abstraction layers, easier to debug&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Memory System: Markdown vs Vector DB
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;atypica&lt;/th&gt;
&lt;th&gt;Mem0&lt;/th&gt;
&lt;th&gt;Core Difference&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Markdown files&lt;/td&gt;
&lt;td&gt;Vector + Graph + KV&lt;/td&gt;
&lt;td&gt;We choose &lt;strong&gt;simple and transparent over precise and complex&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Full loading&lt;/td&gt;
&lt;td&gt;Semantic retrieval&lt;/td&gt;
&lt;td&gt;When context window is large enough, full text is better&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;User-editable&lt;/td&gt;
&lt;td&gt;AI black box&lt;/td&gt;
&lt;td&gt;User trust comes from transparency&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Why different?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Mem0 pursues precise retrieval, using multiple databases in hybrid.&lt;/p&gt;

&lt;p&gt;We believe, for personal assistants:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Simple solution is enough&lt;/strong&gt;: User memory typically &amp;lt; 10K&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transparent beats precise&lt;/strong&gt;: Users can view and edit memory&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Gets better as context grows&lt;/strong&gt;: At 1M tokens in the future, this approach will crush Vector DB&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Core Philosophy Differences
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;atypica's choices&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simple, transparent, controllable&lt;/li&gt;
&lt;li&gt;Adapt to LLM characteristics (large context, non-determinism)&lt;/li&gt;
&lt;li&gt;Start from real pain points, not pursuing architectural perfection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Mainstream frameworks' choices&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Precise, complex, automatic&lt;/li&gt;
&lt;li&gt;Port traditional software engineering patterns&lt;/li&gt;
&lt;li&gt;Pursue generality and flexibility&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Who's right or wrong?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Neither is wrong. It's just:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Our scenario (personal research assistant) suits simple approaches better&lt;/li&gt;
&lt;li&gt;As context windows grow, simple approaches become better&lt;/li&gt;
&lt;li&gt;User trust comes from transparency, not AI magic&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  V. Quantitative Impact
&lt;/h2&gt;

&lt;p&gt;Specific impact from three evolutions:&lt;/p&gt;

&lt;h3&gt;
  
  
  Code Complexity
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Duplicate code:
Before: 1,211 lines (three agent wrappers)
After: 0 lines
Reduction: 100%

Total lines of code:
Before: 1,211 lines (duplicates) + others
After: 577 lines (base) + 380 lines (configs) = 957 lines
Net reduction: 254 lines (21%)

Cyclomatic Complexity (code complexity metric):
Before: avg 12.3
After: avg 6.7
Reduction: 45%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Development Efficiency
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;th&gt;Improvement&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Add new research method&lt;/td&gt;
&lt;td&gt;12 files, 2-3 days&lt;/td&gt;
&lt;td&gt;3 files, 2-3 hours&lt;/td&gt;
&lt;td&gt;10x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Add new capability (MCP)&lt;/td&gt;
&lt;td&gt;Modify 3 places, 1 day&lt;/td&gt;
&lt;td&gt;Modify 1 place, 2 hours&lt;/td&gt;
&lt;td&gt;4x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fix bug&lt;/td&gt;
&lt;td&gt;Change 3 agents&lt;/td&gt;
&lt;td&gt;Change 1 base&lt;/td&gt;
&lt;td&gt;3x&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  System Performance
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Token consumption (with prompt cache):
- studyLog generation: ~2K tokens (~$0.002)
- Memory update: ~5K tokens (~$0.005)
- Average per conversation: +$0.007

Response time:
- Memory loading: +50ms (non-blocking)
- Plan Mode: +2s (one-time)
- studyLog generation: background, doesn't affect response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Cost and performance impact negligible.&lt;/p&gt;

&lt;h3&gt;
  
  
  User Experience
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Intent clarification:
Before: average 3.2 conversation rounds
After: 1 plan display + 1 confirmation
Improvement: 3x efficiency

AI "memory":
Before: repetitive questions every conversation
After: auto-load user preferences
Improvement: personalized experience

Research startup time:
Before: ~5 minutes (multiple rounds of clarification)
After: ~1 minute (one-click confirm)
Improvement: 5x efficiency
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  VI. Lessons Learned
&lt;/h2&gt;

&lt;p&gt;What did we learn from three evolutions?&lt;/p&gt;

&lt;h3&gt;
  
  
  What We Did Right
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Incremental refactoring, not big bang&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;We didn't rewrite the entire system at once. Three evolutions, each step:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Delivers value independently&lt;/li&gt;
&lt;li&gt;Maintains backward compatibility (keeping &lt;code&gt;analyst.studySummary&lt;/code&gt; field)&lt;/li&gt;
&lt;li&gt;Can be rolled back&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This let us quickly validate ideas and reduce risk.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Start from real pain points&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Don't pursue architectural perfection, instead:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Message-driven: because adding &lt;code&gt;discussionChat&lt;/code&gt; was too complex&lt;/li&gt;
&lt;li&gt;Unified execution: because duplicate code was too much&lt;/li&gt;
&lt;li&gt;Persistent memory: because users reported AI forgetfulness&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let problems drive design, not design drive problems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Embrace LLM characteristics&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Don't treat LLMs as traditional software:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Don't hand-write state machines, let AI infer state from conversations&lt;/li&gt;
&lt;li&gt;Leverage large context windows, rather than pursuing precise retrieval&lt;/li&gt;
&lt;li&gt;Let AI generate studyLog, rather than hand-writing parsers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Adapt to LLM's capability boundaries, rather than fighting them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Costs We Paid
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Learning curve for abstraction layer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;baseAgentRequest&lt;/code&gt; requires understanding to modify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;6 phases of execution flow&lt;/li&gt;
&lt;li&gt;Timing of &lt;code&gt;customPrepareStep&lt;/code&gt; and &lt;code&gt;customOnStepFinish&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Generic constraints and type inference&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But: clear interfaces and documentation lowered the barrier.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Cost of on-demand generation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;studyLog generation requires LLM call (~$0.002/time).&lt;/p&gt;

&lt;p&gt;But:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prompt cache reduces cost by 90%&lt;/li&gt;
&lt;li&gt;Architectural benefits &amp;gt;&amp;gt; small cost&lt;/li&gt;
&lt;li&gt;Acceptable&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;3. Limitations of simple solutions&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Markdown memory isn't suitable for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Large-scale knowledge bases (&amp;gt; 100K tokens)&lt;/li&gt;
&lt;li&gt;Complex relational queries&lt;/li&gt;
&lt;li&gt;Multi-dimensional retrieval&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Good enough for personal assistant scenarios&lt;/li&gt;
&lt;li&gt;Can upgrade to Vector DB in the future&lt;/li&gt;
&lt;li&gt;Solve 80% of problems first&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Unexpected Benefits
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Confidence from type safety&lt;/strong&gt;&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;// Fully type-safe tool handling&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;step&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toolResults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;t&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dynamic&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;t&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toolName&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;ToolName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;generateReport&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;StaticToolResult&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;Pick&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;StudyToolSet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ToolName&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;generateReport&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;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="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;output&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="nx"&gt;tool&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reportToken&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// ← TypeScript knows this field exists&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During refactoring, the compiler catches 99% of issues.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Flexibility of configuration-driven&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Adding webhook integration only requires:&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;// baseAgentRequest.ts&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;webhookUrl&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;sendWebhook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;webhookUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;step&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;All agents automatically gain new capability, no config changes needed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Power of messages as protocol&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Plan Mode and Study Agent communicate through messages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Decoupled: can be modified independently&lt;/li&gt;
&lt;li&gt;Without losing context: complete decision process in messages&lt;/li&gt;
&lt;li&gt;Traceable: can replay when problems occur&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was an unexpected benefit.&lt;/p&gt;




&lt;h2&gt;
  
  
  VII. Future Directions
&lt;/h2&gt;

&lt;p&gt;Three evolutions brought atypica closer to general-purpose agents. But there's more to do.&lt;/p&gt;

&lt;h3&gt;
  
  
  Short-term (3-6 months)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;1. Skills Library&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Further modularize tools&lt;/li&gt;
&lt;li&gt;Users can compose their own agents&lt;/li&gt;
&lt;li&gt;Like GPTs, but more flexible&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;2. Multi-Agent Collaboration&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Not just serial execution&lt;/li&gt;
&lt;li&gt;Parallel research, cross-validation&lt;/li&gt;
&lt;li&gt;Like AutoGPT, but more controllable&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Long-term (1-2 years)
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;3. Evolve toward GEA&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GEA = General Execution Architecture&lt;/li&gt;
&lt;li&gt;Not just research agents, but a universal AI Agent execution framework&lt;/li&gt;
&lt;li&gt;Can run any type of agent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;4. Self-Improving Agents&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Agents learn from past executions&lt;/li&gt;
&lt;li&gt;Continuously optimize prompts and strategies&lt;/li&gt;
&lt;li&gt;Get smarter with use&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Unchanging Principles
&lt;/h3&gt;

&lt;p&gt;No matter how we evolve, we stick to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Simple beats complex&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Transparent beats black box&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;User control beats AI automation&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  VIII. Conclusion
&lt;/h2&gt;

&lt;p&gt;Building AI Agent systems is not a simple extension of traditional software engineering.&lt;/p&gt;

&lt;p&gt;We need to rethink:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What is state? (&lt;strong&gt;Conversation history&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;What is an interface? (&lt;strong&gt;Message protocol&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;What is control flow? (&lt;strong&gt;AI reasoning&lt;/strong&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;atypica's three evolutions are essentially three cognitive upgrades:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;From database thinking → data flow thinking&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Don't maintain explicit state, infer state from messages&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;From code reuse → configuration-driven&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Don't pursue perfect abstraction, use configuration to express differences&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;From stateless → memory-enhanced&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Don't rely on precise retrieval, use simple and transparent methods&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;These choices may not be the most "advanced."&lt;/p&gt;

&lt;p&gt;But they are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Simple&lt;/strong&gt;: easy to understand, easy to debug&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transparent&lt;/strong&gt;: users know what AI is doing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Controllable&lt;/strong&gt;: users can intervene and adjust&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Good enough&lt;/strong&gt;: solve 80% of problems&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And this, perhaps, is the key to building &lt;strong&gt;reliable&lt;/strong&gt; AI systems.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://atypica.ai/blog/towards-general-agent" rel="noopener noreferrer"&gt;https://atypica.ai/blog/towards-general-agent&lt;/a&gt;&lt;/p&gt;

</description>
      <category>systemdesign</category>
      <category>architecture</category>
      <category>ai</category>
      <category>agents</category>
    </item>
    <item>
      <title>Designing Natural AI Memory: Why It Feels Awkward and How to Fix It</title>
      <dc:creator>web3nomad.eth</dc:creator>
      <pubDate>Sun, 11 Jan 2026 09:41:34 +0000</pubDate>
      <link>https://forem.com/web3nomad/designing-natural-ai-memory-why-it-feels-awkward-and-how-to-fix-it-46jc</link>
      <guid>https://forem.com/web3nomad/designing-natural-ai-memory-why-it-feels-awkward-and-how-to-fix-it-46jc</guid>
      <description>&lt;p&gt;You've built memory into your AI. Users can now have conversations that reference past interactions. Great!&lt;/p&gt;

&lt;p&gt;Except... users feel weird about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Here's what usually happens:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User:&lt;/strong&gt; "Help me research Gen-Z skincare trends"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI:&lt;/strong&gt; &lt;em&gt;(researches, provides insights)&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User:&lt;/strong&gt; &lt;em&gt;(two weeks later)&lt;/em&gt; "Now help me with Gen-Z gaming behavior"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI:&lt;/strong&gt; "According to your saved preferences from our conversation on March 5th at 2:34 PM, you prefer detailed analysis with customer insights. I also notice from your previous research on March 5th that you focus on Gen-Z demographics. Based on these stored memories..."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User:&lt;/strong&gt; 😬&lt;/p&gt;

&lt;p&gt;That AI just turned helpful into awkward in one response.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Users Actually Want
&lt;/h2&gt;

&lt;p&gt;Same scenario, different approach:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User:&lt;/strong&gt; &lt;em&gt;(two weeks later)&lt;/em&gt; "Now help me with Gen-Z gaming behavior"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI:&lt;/strong&gt; "Interesting—continuing that Olay campaign logic from last time where emotional connection beat price sensitivity. Gaming probably works the same way. Let me look..."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User:&lt;/strong&gt; 😊&lt;/p&gt;

&lt;p&gt;See the difference? The second AI &lt;strong&gt;understood&lt;/strong&gt; without &lt;strong&gt;announcing&lt;/strong&gt; it understood. It referenced a specific past project naturally, like a colleague would.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Big Insight
&lt;/h2&gt;

&lt;p&gt;Users don't want AI that "remembers." They want AI that &lt;strong&gt;gets them&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Even better: AI that &lt;strong&gt;knows what they need before they do&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Think about your best friend. They don't say "I remember you telling me on Tuesday you like coffee." They just show up with coffee.&lt;/p&gt;

&lt;p&gt;And sometimes, they show up with coffee when you didn't even realize you needed one.&lt;/p&gt;

&lt;p&gt;That's the experience you're building toward.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Get There
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Stop Making AI Sound Like a Database
&lt;/h3&gt;

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

&lt;ul&gt;
&lt;li&gt;"Your stored preference indicates..."&lt;/li&gt;
&lt;li&gt;"According to our conversation history..."&lt;/li&gt;
&lt;li&gt;"I remember you mentioned..."&lt;/li&gt;
&lt;li&gt;"Based on your saved settings..."&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;"Continuing that Olay campaign logic..."&lt;/li&gt;
&lt;li&gt;"Like you found last time with skincare..."&lt;/li&gt;
&lt;li&gt;"You usually want the detailed version, so..."&lt;/li&gt;
&lt;li&gt;Just do it without saying anything&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The pattern: Reference naturally or stay quiet. Never announce the fact that you're referencing memory.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Make Preferences Actually Do Something
&lt;/h3&gt;

&lt;p&gt;I see this constantly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User: "I prefer detailed analysis"
[AI stores it]
*Next conversation*
AI: "Would you like a brief or detailed analysis?"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're storing preferences but not using them, you're just building a surveillance system with extra steps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix it:&lt;/strong&gt; When AI knows user wants detailed analysis, just output detailed analysis. Skip the question. That's what memory is FOR.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Proactively Connect the Dots
&lt;/h3&gt;

&lt;p&gt;This is where good memory becomes &lt;strong&gt;indispensable&lt;/strong&gt;:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User:&lt;/strong&gt; "Help me with Gen-Z beauty trends"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI:&lt;/strong&gt; "You researched skincare three months ago and found emotional authenticity mattered more than price. That Z-gen insight probably applies here too—want me to focus on emotional drivers?"&lt;/p&gt;

&lt;p&gt;See what happened? AI didn't wait for the user to connect the dots. It:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Recalled a relevant past research&lt;/li&gt;
&lt;li&gt;Identified the pattern (Gen-Z consumer behavior)&lt;/li&gt;
&lt;li&gt;Offered to apply the insight proactively&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Another example:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User:&lt;/strong&gt; "I need campaign ideas for a new product launch"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AI:&lt;/strong&gt; "Last time with the PH launch you said you wanted to try AI-generated video. Runway just released their new model—should I include some video concepts?"&lt;/p&gt;

&lt;p&gt;The AI remembered an unexplored interest and brought it up at the perfect moment.&lt;/p&gt;

&lt;p&gt;This is what "understanding you better than you understand yourself" looks like in practice.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Don't Store Everything—Store Signposts
&lt;/h3&gt;

&lt;p&gt;Here's a mistake I see everywhere:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Memory Database:
- March 5: User researched Gen-Z skincare. Found emotional triggers
  important. Interviewed 8 personas. Key insight was that authentic
  brand voice matters more than celebrity endorsements. Focused on
  Instagram and TikTok as primary channels. Budget was $50k. Client
  was Olay. Campaign theme was "Real Beauty Real Talk"...
- April 2: User researched Gen-Z gaming. Found community matters.
  Interviewed 5 personas. Discord important. Twitch streamers...
- May 1: User researched Gen-Z fashion...
[... 500 more entries like this]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your database just exploded. Worse, AI can't find patterns because it's drowning in details.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Better:&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;Memory:
- Research history: Skincare Gen-Z/Olay (emotions &amp;gt; price), Gaming Gen-Z
  (community matters), Fashion Gen-Z (sustainability key)
- Pattern: Always asks about Gen-Z consumer behavior
- Unexplored interests: Mentioned wanting to try AI video generation
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One-line summaries with just enough detail to trigger recall. That's it. The full research already lives in your database—memory should just help AI &lt;strong&gt;connect dots&lt;/strong&gt; across conversations.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Make Memory Tangible
&lt;/h3&gt;

&lt;p&gt;Users trust memory more when they can see it:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Timeline view:&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;You and I have worked together for 6 months
↓
5 research projects completed
↓
3 recurring themes identified
↓
Your campaigns now start 40% faster
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Milestone moments:&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;AI: "We've been working together for a year now—from that first positioning
discussion for Olay to today's Gen-Z gaming research. Want to see how your
research focus has evolved?"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Knowledge graph:&lt;/strong&gt;&lt;br&gt;
Show what AI remembers and how pieces connect. Like Notion's "Mentioned in" feature, but for your brain.&lt;/p&gt;

&lt;p&gt;This isn't just nice-to-have. Visible memory builds trust. Users understand what AI knows and what it doesn't.&lt;/p&gt;

&lt;h3&gt;
  
  
  6. Let Understanding Grow Over Time
&lt;/h3&gt;

&lt;p&gt;Here's what instant omniscience looks like:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User:&lt;/strong&gt; &lt;em&gt;(first conversation ever)&lt;/em&gt;&lt;br&gt;
&lt;strong&gt;AI:&lt;/strong&gt; "I've analyzed your communication patterns and determined you prefer structured analysis with executive summaries. I've also noted your focus on actionable insights over theoretical frameworks..."&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User:&lt;/strong&gt; "...we literally just met?"&lt;/p&gt;

&lt;p&gt;Feels like AI is pretending to know you when it doesn't.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Better experience:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1st time:&lt;/strong&gt; User explains everything. AI takes notes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3rd time:&lt;/strong&gt; AI starts anticipating. "Should I include user interviews like last time?"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;10th time:&lt;/strong&gt; AI challenges. "You usually care about Gen-Z, but this product is really for Millennials. Sure you want Gen-Z angle?"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1 year anniversary:&lt;/strong&gt; "We've done 12 projects together. You've shifted from purely tactical campaigns to strategic positioning. Want me to default to strategic framing now?"&lt;/p&gt;

&lt;p&gt;That progression from helper → anticipator → thought partner → strategic advisor feels natural. Instant expertise feels fake.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Three Ways to Kill Your Memory Feature
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Say "I Remember" Too Much
&lt;/h3&gt;

&lt;p&gt;Every AI builder tries this at some point:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I remember you like detailed analysis. I remember you focus on Gen-Z. I remember you prefer interviews over surveys. I remember..."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Users feel watched. Like AI is showing off that it's tracking them.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; Reference naturally when it adds value. Otherwise shut up about it.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Never Use What You Stored
&lt;/h3&gt;

&lt;p&gt;The opposite problem:&lt;/p&gt;

&lt;p&gt;AI accumulates thousands of memory entries... and never references any of them. Users wonder why you asked for all that information.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix:&lt;/strong&gt; If a stored fact doesn't change AI behavior, delete it. Every memory item must earn its keep.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Update at the Wrong Time
&lt;/h3&gt;

&lt;p&gt;Tempting: Update memory after every single message.&lt;/p&gt;

&lt;p&gt;Reality: Most conversation is exploration. Users trying things out. Not worth storing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Better:&lt;/strong&gt; Update at natural completion points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Finished a research project? Update.&lt;/li&gt;
&lt;li&gt;Solved a problem? Update.&lt;/li&gt;
&lt;li&gt;Completed a document? Update.&lt;/li&gt;
&lt;li&gt;Hit a milestone? Update.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You get complete context, stable information, and clearer patterns. Plus it's way cheaper.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Moment You're Building Toward
&lt;/h2&gt;

&lt;p&gt;You've succeeded when a user has this experience:&lt;/p&gt;

&lt;p&gt;They ask for something. AI does exactly what they wanted, exactly how they wanted it.&lt;/p&gt;

&lt;p&gt;Better yet: AI suggests something they didn't even know they wanted.&lt;/p&gt;

&lt;p&gt;User pauses.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Wait... this AI actually gets me."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That moment doesn't come from AI announcing "I remember your preferences."&lt;/p&gt;

&lt;p&gt;It comes from AI &lt;strong&gt;doing the right thing before being asked&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It comes from AI saying "You researched skincare three months ago—that insight might help here."&lt;/p&gt;

&lt;p&gt;It comes from AI connecting dots the user didn't even see yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Test
&lt;/h2&gt;

&lt;p&gt;Next time you're about to make AI say "I remember..." or "based on your stored preferences...", try this instead:&lt;/p&gt;

&lt;p&gt;Just do what the memory says to do. Don't announce it.&lt;/p&gt;

&lt;p&gt;Or better: &lt;strong&gt;Proactively connect it to something the user is working on now.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If the user notices and appreciates it—you nailed it.&lt;/p&gt;

&lt;p&gt;If the user doesn't notice—even better. That's memory working perfectly. Invisible until that one moment when it clicks.&lt;/p&gt;

&lt;p&gt;If the user says "how did you know?"—you've transcended. That's when memory becomes magic.&lt;/p&gt;




&lt;p&gt;Good memory feels like understanding, not surveillance.&lt;/p&gt;

&lt;p&gt;Great memory feels like mind-reading.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://atypica.ai/blog/designing-natural-ai-memory" rel="noopener noreferrer"&gt;https://atypica.ai/blog/designing-natural-ai-memory&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>ux</category>
    </item>
  </channel>
</rss>
